diff --git a/Assets/Act-4 Azure.unity b/Assets/Act-4 Azure.unity index 65ec1f7..959671c 100644 --- a/Assets/Act-4 Azure.unity +++ b/Assets/Act-4 Azure.unity @@ -259,7 +259,7 @@ MonoBehaviour: externalRootMotion: 0 externalHeadRotation: 0 externalHandRotations: 0 - fingerOrientations: 0 + fingerOrientations: 1 moveRate: 1 smoothFactor: 10 lateUpdateAvatar: 0 @@ -361,8 +361,8 @@ MonoBehaviour: getColorFrames: 0 getInfraredFrames: 0 getPoseFrames: 0 - getBodyFrames: 1 - syncDepthAndColor: 1 + getBodyFrames: 3 + syncDepthAndColor: 0 syncBodyAndDepth: 1 minUserDistance: 0 maxUserDistance: 0 @@ -733,13 +733,13 @@ VisualEffect: - m_Value: 0 m_Name: Antenna Point Size m_Overridden: 1 - - m_Value: 1 + - m_Value: 4 m_Name: Point Lifetime Max m_Overridden: 1 - - m_Value: 0 + - m_Value: 70000 m_Name: Point Rate m_Overridden: 1 - - m_Value: 0 + - m_Value: 1 m_Name: Turbulence Drag m_Overridden: 1 - m_Value: 1 @@ -751,16 +751,16 @@ VisualEffect: - m_Value: 0 m_Name: Noise m_Overridden: 1 - - m_Value: 0.1 + - m_Value: 0.01 m_Name: Pont Lifetime Min m_Overridden: 1 - - m_Value: 0 + - m_Value: 1 m_Name: Turbulences m_Overridden: 1 - - m_Value: 0 + - m_Value: 1 m_Name: Turbulences Frequency m_Overridden: 1 - - m_Value: 0 + - m_Value: 0.334 m_Name: Turbulences Ratio m_Overridden: 1 - m_Value: 1.99 @@ -808,7 +808,7 @@ VisualEffect: - m_Value: {x: 1, y: 0.6917327, z: 0, w: 1} m_Name: Line Color m_Overridden: 1 - - m_Value: {x: 1, y: 1, z: 1, w: 1} + - m_Value: {x: 1, y: 0.77925056, z: 0, w: 1} m_Name: Point Color m_Overridden: 1 m_Uint: @@ -853,7 +853,7 @@ MonoBehaviour: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 365239546} - m_Enabled: 1 + m_Enabled: 0 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 30ddee29d67e741429d68f6ce139af61, type: 3} m_Name: @@ -1160,8 +1160,8 @@ Transform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 624895792} serializedVersion: 2 - m_LocalRotation: {x: -0, y: 0.92070276, z: -0, w: -0.39026445} - m_LocalPosition: {x: 3.04, y: -0.73, z: 8.950001} + m_LocalRotation: {x: 0.014272982, y: -0.70204425, z: -0.024736581, w: 0.7115604} + m_LocalPosition: {x: 5.4510007, y: -0.05499983, z: -0.5339999} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -1303,7 +1303,7 @@ Camera: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 624895792} - m_Enabled: 0 + m_Enabled: 1 serializedVersion: 2 m_ClearFlags: 1 m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} @@ -1759,8 +1759,8 @@ Transform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1321662289} serializedVersion: 2 - m_LocalRotation: {x: 0.70224655, y: 0.08459074, z: -0.10908293, w: 0.6984233} - m_LocalPosition: {x: 0.85, y: 7.47, z: 0.6} + m_LocalRotation: {x: 0.7048889, y: -0.05863019, z: -0.24756199, w: 0.6621234} + m_LocalPosition: {x: 3.83, y: 5.88, z: -1.2399998} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -2124,7 +2124,7 @@ Camera: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1823688464} - m_Enabled: 1 + m_Enabled: 0 serializedVersion: 2 m_ClearFlags: 1 m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} @@ -2177,7 +2177,7 @@ Transform: m_GameObject: {fileID: 1823688464} serializedVersion: 2 m_LocalRotation: {x: -0, y: 0.99936223, z: -0, w: -0.035710618} - m_LocalPosition: {x: 0.15265846, y: -1.0031738, z: 8.098137} + m_LocalPosition: {x: 1.48, y: -0.55, z: 2.45} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] diff --git a/Assets/Fullscreen.meta b/Assets/Fullscreen.meta new file mode 100644 index 0000000..872cd32 --- /dev/null +++ b/Assets/Fullscreen.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: e58df2ef30bd5d94c9987dfbf4bd8838 +folderAsset: yes +timeCreated: 1497142680 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor.meta b/Assets/Fullscreen/Editor.meta new file mode 100644 index 0000000..c91576c --- /dev/null +++ b/Assets/Fullscreen/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: e88d8747d7da76847b620be9aa548d88 +folderAsset: yes +timeCreated: 1471207248 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/After.cs b/Assets/Fullscreen/Editor/After.cs new file mode 100644 index 0000000..95cc330 --- /dev/null +++ b/Assets/Fullscreen/Editor/After.cs @@ -0,0 +1,52 @@ +using System; +using System.Diagnostics; +using UnityEditor; +using UnityEngine; + +namespace FullscreenEditor { + /// Utility class for running async tasks within the main thread. + public static class After { + + /// Wait for a condition to become true, then executes the callback. + /// Function that will be called every frame that returns whether to invoke the callback or not. + /// The callback to be called when the condition becomes true. + /// Maximum time to wait in milliseconds before cancelling the callback. + public static void Condition(Func condition, Action callback, double timeoutMs = 0d) { + var update = new EditorApplication.CallbackFunction(() => { }); + var timeoutsAt = EditorApplication.timeSinceStartup + (timeoutMs / 1000d); + var stack = new StackFrame(1, true); + + update = () => { + if (timeoutMs > 0d && EditorApplication.timeSinceStartup >= timeoutsAt) { + EditorApplication.update -= update; + Logger.Error("Condition timedout at {0}:{1}", stack.GetFileName(), stack.GetFileLineNumber()); + return; + } + + if (condition()) { + EditorApplication.update -= update; + callback(); + } + }; + + EditorApplication.update += update; + } + + /// Wait for the given amount of editor frames, then executes the callback. + /// The number of frames to wait for. + /// The callback to be called after the specified frames. + public static void Frames(int frames, Action callback) { + var f = 0; + Condition(() => f++ >= frames, callback); + } + + /// Wait for the given time, then executes the callback. + /// How long to wait until calling the callback, in milliseconds. + /// The callback to be called after the specified time. + public static void Milliseconds(double milliseconds, Action callback) { + var end = EditorApplication.timeSinceStartup + (milliseconds / 1000f); + Condition(() => EditorApplication.timeSinceStartup >= end, callback); + } + + } +} diff --git a/Assets/Fullscreen/Editor/After.cs.meta b/Assets/Fullscreen/Editor/After.cs.meta new file mode 100644 index 0000000..66fcd4d --- /dev/null +++ b/Assets/Fullscreen/Editor/After.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0e5019b6f2b222d42aa01853d0e67227 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Cmd.cs b/Assets/Fullscreen/Editor/Cmd.cs new file mode 100644 index 0000000..69e8b3a --- /dev/null +++ b/Assets/Fullscreen/Editor/Cmd.cs @@ -0,0 +1,90 @@ +using System; +using System.Diagnostics; +using System.Text; +using UnityEngine; + +namespace FullscreenEditor { + public static class Cmd { + + public static string Run(string command, params object[] formatArgs) { + return Run(command, false, formatArgs); + } + + public static string Run(string command, bool asAdmin, params object[] formatArgs) { + command = string.Format(command, formatArgs); + + var stdout = string.Empty; + var stderr = string.Empty; + var exitCode = Run(command, asAdmin, out stdout, out stderr); + + if (exitCode == 0) + return stdout.Trim(); + + throw new Exception(string.Format("Command {0} exited with code {1}", command, exitCode)); + } + + public static int Run(string command, bool asAdmin, out string stdout, out string stderr) { + var proc = new Process(); + var stdoutBuilder = new StringBuilder(); + var stderrBuilder = new StringBuilder(); + + proc.EnableRaisingEvents = true; + + if (Application.platform == RuntimePlatform.WindowsEditor) + proc.StartInfo = new ProcessStartInfo() { + FileName = "cmd.exe", + Arguments = "/C \"" + command + "\"", + UseShellExecute = asAdmin, + RedirectStandardError = !asAdmin, + RedirectStandardOutput = !asAdmin, + Verb = asAdmin? "runas": "", + CreateNoWindow = !asAdmin, + WorkingDirectory = Environment.CurrentDirectory + }; + else + proc.StartInfo = new ProcessStartInfo() { + FileName = "/bin/bash", + Arguments = "-c \"" + command + "\"", + UseShellExecute = asAdmin, + RedirectStandardError = !asAdmin, + RedirectStandardOutput = !asAdmin, + CreateNoWindow = !asAdmin, + WorkingDirectory = Environment.CurrentDirectory + }; + + if (!asAdmin) { + proc.OutputDataReceived += (sender, args) => { + if (!string.IsNullOrEmpty(args.Data)) + stdoutBuilder.AppendLine(args.Data); + }; + + proc.ErrorDataReceived += (sender, args) => { + if (!string.IsNullOrEmpty(args.Data)) + stderrBuilder.AppendLine(args.Data); + }; + } + + //proc.Exited += (sender, args) => { + // Debug.LogWarningFormat("Command {0} exited with code {1}", command, proc.ExitCode); + //}; + + if (proc.Start()) { + if (!asAdmin) { + proc.BeginOutputReadLine(); + proc.BeginErrorReadLine(); + } + proc.WaitForExit(); + + stdout = stdoutBuilder.ToString(); + stderr = stderrBuilder.ToString(); + + Logger.Debug("{3}: {0}\nstdout: {1}\nstderr: {2}", command, stdout, stderr, proc.ExitCode); + + return proc.ExitCode; + } + + throw new Exception(string.Format("Failed to start process for {0}", command)); + } + + } +} diff --git a/Assets/Fullscreen/Editor/Cmd.cs.meta b/Assets/Fullscreen/Editor/Cmd.cs.meta new file mode 100644 index 0000000..358e95d --- /dev/null +++ b/Assets/Fullscreen/Editor/Cmd.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6754a626da46d9a788cf7f6da2f00106 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/DisableSceneView.cs b/Assets/Fullscreen/Editor/DisableSceneView.cs new file mode 100644 index 0000000..762374f --- /dev/null +++ b/Assets/Fullscreen/Editor/DisableSceneView.cs @@ -0,0 +1,151 @@ +using UnityEditor; +using UnityEngine; + +namespace FullscreenEditor { + public class DisableSceneView { + + // TODO: Patcher resets the method to original after a while + // TODO: or the shouldSkipRender is not being properly calculated + private static Patcher patcher = null; + + public static bool RenderingDisabled { + get { + return patcher == null ? false : patcher.IsPatched(); + } + set { + if(patcher == null) return; + + if(value == patcher.IsPatched()) + return; + else if(!value) + patcher.Revert(); + else if(FullscreenPreferences.DisableSceneViewRendering) + patcher.SwapMethods(); + + if(!value || FullscreenPreferences.DisableSceneViewRendering) + foreach(var c in SceneView.GetAllSceneCameras()) + c.gameObject.SetActive(!value); + + Logger.Debug("{0} Scene View Rendering", value ? "Disabled" : "Enabled"); + SceneView.RepaintAll(); + } + } + + [InitializeOnLoadMethod] + private static void Init() { + if(!Patcher.IsSupported()) return; + + var sceneGUIName = ReflectionUtility.HasMethod(typeof(SceneView), "OnSceneGUI") ? "OnSceneGUI" : "OnGUI"; + + patcher = new Patcher( + typeof(SceneView).FindMethod(sceneGUIName), // Original method + typeof(DisableSceneView).FindMethod("OnGUI") // Replacement + ); + + SceneView.beforeSceneGui += OnBeforeSceneGUI; + + // Initial + RenderingDisabled = Fullscreen.GetAllFullscreen().Length > 0; + + // On preferences change + FullscreenPreferences.DisableSceneViewRendering.OnValueSaved += (v) => + RenderingDisabled = v && Fullscreen.GetAllFullscreen().Length > 0; + + // On fullscreen open + FullscreenCallbacks.afterFullscreenOpen += (f) => + RenderingDisabled = true; + + // Disable the patching if we're the last fullscreen open + FullscreenCallbacks.afterFullscreenClose += (f) => { + if(Fullscreen.GetAllFullscreen().Length <= 1) + RenderingDisabled = false; + }; + } + + private static void OnBeforeSceneGUI(SceneView sceneView) { + var shouldRender = !RenderingDisabled || Fullscreen.GetFullscreenFromView(new ViewPyramid(sceneView).Container, false); + sceneView.autoRepaintOnSceneChange = shouldRender; + } + + // This should not be a static method, as static has no this + // However, the 'this' in the method is unreliable and should be casted before using + private void OnGUI() { + var _this = (object)this as SceneView; + var vp = new ViewPyramid(_this); + var shouldRender = Fullscreen.GetFullscreenFromView(vp.Container, false); // Render if this window is in fullscreen + + if(shouldRender) { + // Do this outside of OnGUI to prevent controls count errors + After.Frames(1, () => EnableRenderingTemporarily()); + } else { + _this.autoRepaintOnSceneChange = false; + CustomOnGUI(); + } + } + + private static class Styles { + + public static readonly GUIStyle textStyle = new GUIStyle("BoldLabel"); + public static readonly GUIStyle backgroundShadow = new GUIStyle("InnerShadowBg"); + public static readonly GUIStyle buttonStyle = new GUIStyle("LargeButton"); + public static readonly GUIStyle secondaryTextStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel); + + static Styles() { + textStyle.wordWrap = true; + textStyle.alignment = TextAnchor.MiddleCenter; + } + + } + + private void CustomOnGUI() { + using(var mainScope = new EditorGUILayout.VerticalScope(Styles.backgroundShadow)) { + using(new GUIColor(Styles.textStyle.normal.textColor * 0.05f)) + GUI.DrawTexture(mainScope.rect, FullscreenUtility.FullscreenIcon, ScaleMode.ScaleAndCrop); + + GUILayout.FlexibleSpace(); + + using(new EditorGUILayout.HorizontalScope()) { + GUILayout.FlexibleSpace(); + + using(new GUIContentColor(Styles.textStyle.normal.textColor)) + GUILayout.Label(FullscreenUtility.FullscreenIcon, Styles.textStyle); + + using(new EditorGUILayout.VerticalScope()) { + GUILayout.Label("Scene View rendering has been disabled\nto improve fullscreen performance", Styles.textStyle); + } + + GUILayout.FlexibleSpace(); + } + + using(new EditorGUILayout.HorizontalScope()) { + GUILayout.FlexibleSpace(); + if(GUILayout.Button("Enable Temporarily", Styles.buttonStyle)) { + // Do this outside of OnGUI to prevent controls count errors + After.Frames(1, () => EnableRenderingTemporarily()); + var _this = (object)this as SceneView; + _this.Focus(); + } + if(GUILayout.Button("Enable Permanently", Styles.buttonStyle)) { + // Do this outside of OnGUI to prevent controls count errors + After.Frames(1, () => EnableRenderingPermanently()); + var _this = (object)this as SceneView; + _this.Focus(); + } + GUILayout.FlexibleSpace(); + } + + GUILayout.Label("* This can be changed later in the preferences", Styles.secondaryTextStyle); + GUILayout.FlexibleSpace(); + } + } + + public static void EnableRenderingTemporarily() { + RenderingDisabled = false; + } + + public static void EnableRenderingPermanently() { + FullscreenPreferences.DisableSceneViewRendering.Value = false; + } + + } +} diff --git a/Assets/Fullscreen/Editor/DisableSceneView.cs.meta b/Assets/Fullscreen/Editor/DisableSceneView.cs.meta new file mode 100644 index 0000000..ed1ac3d --- /dev/null +++ b/Assets/Fullscreen/Editor/DisableSceneView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5148452bd855eb74cb938ba58e6ce9b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Displays.meta b/Assets/Fullscreen/Editor/Displays.meta new file mode 100644 index 0000000..4d6e9a2 --- /dev/null +++ b/Assets/Fullscreen/Editor/Displays.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 185b2b5b57344834298a7bbe0defe069 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Displays/DisplayInfo.windows.cs b/Assets/Fullscreen/Editor/Displays/DisplayInfo.windows.cs new file mode 100644 index 0000000..04460b5 --- /dev/null +++ b/Assets/Fullscreen/Editor/Displays/DisplayInfo.windows.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using FullscreenEditor.Windows; +using UnityEditorInternal; +using UnityEngine; + +namespace FullscreenEditor { + + [System.Serializable] + public class DisplayInfo { + public bool PrimaryDisplay; + + public float ScreenHeight { + get { return MonitorArea.yMax - MonitorArea.yMin; } + } + public float ScreenWidth { + get { return MonitorArea.xMax - MonitorArea.xMin; } + } + + public float scaleFactor2; + + public int LogicalScreenHeight; + public int PhysicalScreenHeight; + + public string DeviceName { + get { return displayDevice.DeviceName; } + } + + public string FriendlyName { + get { return displayDevice.DeviceString; } + } + + public Rect MonitorArea; + public Rect WorkArea; + + internal DevMode devMode; + internal DisplayDevice displayDevice; + + public Rect DpiCorrectedArea { + get { + var firstDisplayInfo = DisplayInfo.GetDisplay(0); + var monitorArea = MonitorArea; + + var origin = monitorArea.min; + var size = monitorArea.size; + + return new Rect( + Mathf.Round(origin.x / firstDisplayInfo.scaleFactor2), + Mathf.Round(origin.y / firstDisplayInfo.scaleFactor2), + Mathf.Round(size.x / scaleFactor2), + Mathf.Round(size.y / scaleFactor2) + ); + } + } + + public Rect UnityCorrectedArea { + get { + var rect = DpiCorrectedArea; + return InternalEditorUtility.GetBoundsOfDesktopAtPoint(rect.center); + } + } + + public Rect PhysicalArea { + get { + return new Rect( + devMode.dmPositionX, + devMode.dmPositionY, + devMode.dmPelsWidth, + devMode.dmPelsHeight + ); + } + } + + public float scaleFactor { + get { return devMode.dmPelsWidth / (MonitorArea.xMax - MonitorArea.xMin); } + } + + public static List GetDisplays() { + var list = new List(); + + try { + User32.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, + (IntPtr hMonitor, IntPtr hdcMonitor, ref NativeRect lprcMonitor, IntPtr dwData) => { + var mi = new MonitorInfoEx(); + mi.Init(); + mi.size = Marshal.SizeOf(mi); + mi.size = 72; + var success = User32.GetMonitorInfo(hMonitor, ref mi); + if (success) { + var di = new DisplayInfo(); + di.MonitorArea = mi.monitor; + di.WorkArea = mi.work; + di.PrimaryDisplay = (mi.flags & 1) != 0; + + di.LogicalScreenHeight = GDI32.GetDeviceCaps(hMonitor, (int)GDI32.DeviceCap.VERTRES); + di.PhysicalScreenHeight = GDI32.GetDeviceCaps(hMonitor, (int)GDI32.DeviceCap.DESKTOPVERTRES); + + // TransformToPixels(0, 0, out var x, out var y); + + uint dpiX; + uint dpiY; + + try { + ShCore.GetDpiForMonitor( + hMonitor, + MonitorDpiType.MDT_EFFECTIVE_DPI, + out dpiX, + out dpiY + ); + } catch { + dpiX = 96; + dpiY = 96; + } + + di.scaleFactor2 = dpiX / 96f; + list.Add(di); + } else { + Logger.Debug("Getting monitor info failed"); + } + + return true; + }, IntPtr.Zero); + + AddAdditionalInfos(list); + } catch (Exception e) { + Logger.Exception(e); + } + + return list; + } + + public static DisplayInfo GetDisplay(int index) { + var displays = GetDisplays(); + + if (displays != null && index >= 0 && index < displays.Count) { + return displays[index]; + } + + return null; + } + + private static void AddAdditionalInfos(List displayInfo) { + for (int id = 0; id < displayInfo.Count; id++) { + var vDevMode = new DevMode(); + var d = new DisplayDevice(); + + d.cb = Marshal.SizeOf(d); + + try { + User32.EnumDisplayDevices(displayInfo[id].DeviceName, 0, ref d, 0); + + d.cb = Marshal.SizeOf(d); + + if ((d.StateFlags & DisplayDeviceStateFlags.AttachedToDesktop) == DisplayDeviceStateFlags.AttachedToDesktop) { + User32.EnumDisplaySettings(displayInfo[id].DeviceName, -1, ref vDevMode); + displayInfo[id].devMode = vDevMode; + } + + displayInfo[id].displayDevice = d; + } catch (Exception e) { + Logger.Exception(e); + } + } + } + + private static void TransformToPixels(double unitX, double unitY, out int pixelX, out int pixelY) { + var hDc = User32.GetDC(IntPtr.Zero); + + if (hDc != IntPtr.Zero) { + var dpiX = GDI32.GetDeviceCaps(hDc, (int)GDI32.DeviceCap.VERTRES); + var dpiY = GDI32.GetDeviceCaps(hDc, (int)GDI32.DeviceCap.DESKTOPVERTRES); + + User32.ReleaseDC(IntPtr.Zero, hDc); + + pixelX = (int)(((double)dpiX / 96) * unitX); + pixelY = (int)(((double)dpiY / 96) * unitY); + } else { + throw new ArgumentNullException("Failed to get DC."); + } + } + + } +} diff --git a/Assets/Fullscreen/Editor/Displays/DisplayInfo.windows.cs.meta b/Assets/Fullscreen/Editor/Displays/DisplayInfo.windows.cs.meta new file mode 100644 index 0000000..bf7f0b7 --- /dev/null +++ b/Assets/Fullscreen/Editor/Displays/DisplayInfo.windows.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9e5f6f85a367fed4bbeabff3e3248d17 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Disposables.cs b/Assets/Fullscreen/Editor/Disposables.cs new file mode 100644 index 0000000..5e89d6b --- /dev/null +++ b/Assets/Fullscreen/Editor/Disposables.cs @@ -0,0 +1,109 @@ +using System; +using UnityEditor; +using UnityEditor.AnimatedValues; +using UnityEngine; + +namespace FullscreenEditor { + + public struct GUIBackgroundColor : IDisposable { + private readonly Color before; + + public GUIBackgroundColor(Color color) { + before = GUI.backgroundColor; + GUI.backgroundColor = color; + } + + public void Dispose() { + GUI.backgroundColor = before; + } + } + + public struct GUIContentColor : IDisposable { + private readonly Color before; + + public GUIContentColor(Color color) { + before = GUI.contentColor; + GUI.contentColor = color; + } + + public void Dispose() { + GUI.contentColor = before; + } + } + + public struct GUIColor : IDisposable { + private readonly Color before; + + public GUIColor(Color color) { + before = GUI.color; + GUI.color = color; + } + + public GUIColor(Color color, float alpha) { + before = GUI.color; + color.a = alpha; + GUI.color = color; + } + + public void Dispose() { + GUI.color = before; + } + } + + public sealed class GUIIndent : IDisposable { + public GUIIndent() { + EditorGUI.indentLevel++; + } + + public GUIIndent(string label) { + EditorGUILayout.LabelField(label); + EditorGUI.indentLevel++; + } + + public void Dispose() { + EditorGUI.indentLevel--; + EditorGUILayout.Separator(); + } + } + + public struct GUIEnabled : IDisposable { + private readonly bool before; + + public GUIEnabled(bool enabled) { + before = GUI.enabled; + GUI.enabled = before && enabled; + } + + public void Dispose() { + GUI.enabled = before; + } + } + + public sealed class GUIFade : IDisposable { + private AnimBool anim; + + public bool Visible { get; private set; } + + public GUIFade() { + Visible = true; + } + + public void SetTarget(bool target) { + if (anim == null) { + anim = new AnimBool(target); + anim.valueChanged.AddListener(() => { + if (EditorWindow.focusedWindow) + EditorWindow.focusedWindow.Repaint(); + }); + } + + anim.target = target; + Visible = EditorGUILayout.BeginFadeGroup(anim.faded); + } + + public void Dispose() { + EditorGUILayout.EndFadeGroup(); + } + } + +} diff --git a/Assets/Fullscreen/Editor/Disposables.cs.meta b/Assets/Fullscreen/Editor/Disposables.cs.meta new file mode 100644 index 0000000..4b1496c --- /dev/null +++ b/Assets/Fullscreen/Editor/Disposables.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 214e5c575686ed341a4cee0e6716c8a3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/FixGameViewMouseInput.cs b/Assets/Fullscreen/Editor/FixGameViewMouseInput.cs new file mode 100644 index 0000000..4443353 --- /dev/null +++ b/Assets/Fullscreen/Editor/FixGameViewMouseInput.cs @@ -0,0 +1,26 @@ +using UnityEditor; + +namespace FullscreenEditor { + [InitializeOnLoad] + // Issue #93 + public class FixGameViewMouseInput { + + static FixGameViewMouseInput() { + FullscreenCallbacks.afterFullscreenOpen += fs => UpdateGameViewArea(fs); + } + + public static void UpdateGameViewArea(FullscreenContainer fs) { + After.Frames(50, () => { + var window = fs.ActualViewPyramid.Window; + if (window && window.IsOfType(Types.PlayModeView)) { + Logger.Debug("Fixing game view area"); + FullscreenUtility.FocusView(FullscreenUtility.GetMainView()); + + // Issue #95, fix Input.mouseScrollDelta + window.Focus(); + } + }); + } + + } +} diff --git a/Assets/Fullscreen/Editor/FixGameViewMouseInput.cs.meta b/Assets/Fullscreen/Editor/FixGameViewMouseInput.cs.meta new file mode 100644 index 0000000..c86a252 --- /dev/null +++ b/Assets/Fullscreen/Editor/FixGameViewMouseInput.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 713b38e38f114da43b6c55d8c9bf5c5d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Fullscreen.cs b/Assets/Fullscreen/Editor/Fullscreen.cs new file mode 100644 index 0000000..3c70df8 --- /dev/null +++ b/Assets/Fullscreen/Editor/Fullscreen.cs @@ -0,0 +1,166 @@ +using System; +using System.Linq; +using System.Runtime.CompilerServices; +using UnityEditor; +using UnityEngine; + +[assembly: InternalsVisibleToAttribute("FullscreenTests")] + +namespace FullscreenEditor { + /// Main entry point for finding, creating and closing . + public static class Fullscreen { + + private static FullscreenContainer[] cachedFullscreen; + private static FullscreenContainer[] cachedFullscreenAll; + + [InitializeOnLoadMethod] + private static void InitCache() { + GetAllFullscreen(false, true); + + FullscreenCallbacks.beforeFullscreenOpen += (f) => GetAllFullscreen(false, true); + FullscreenCallbacks.afterFullscreenOpen += (f) => GetAllFullscreen(false, true); + FullscreenCallbacks.beforeFullscreenClose += (f) => GetAllFullscreen(false, true); + FullscreenCallbacks.afterFullscreenClose += (f) => GetAllFullscreen(false, true); + } + + /// Return all instances. + /// Allow returning cached content. + /// Do not return fullscreen containers that don't have a valid ContainerWindow. + public static FullscreenContainer[] GetAllFullscreen(bool cached = true, bool ignoreUnknownState = true) { + + if(cached && cachedFullscreen != null && cachedFullscreenAll != null) + return ignoreUnknownState ? + cachedFullscreen : + cachedFullscreenAll; + + cachedFullscreenAll = Resources.FindObjectsOfTypeAll(); + + if(!ignoreUnknownState) + return cachedFullscreenAll; + + cachedFullscreen = cachedFullscreenAll + .Where(fs => fs.m_dst.Container != null) + .ToArray(); + + return cachedFullscreen; + } + + /// Get the on the given point, or null if there is none. + public static FullscreenContainer GetFullscreenOnPoint(Vector2 point) { + return GetAllFullscreen() + .FirstOrDefault(fullscreen => fullscreen.Rect.Contains(point)); + } + + /// Get the that overlaps the given rect, or null if there is none. + public static FullscreenContainer GetFullscreenOnRect(Rect rect) { + return GetAllFullscreen() + .FirstOrDefault(fullscreen => fullscreen.Rect.Overlaps(rect)); + } + + /// Returns the parent for a given view or window, or null if it's not in fullscreen. + /// Compare by the root view, otherwise compare by the container. + public static FullscreenContainer GetFullscreenFromView(ScriptableObject viewOrWindow, bool rootView = true) { + if(!viewOrWindow) + return null; + + var pyramid = new ViewPyramid(viewOrWindow); + + return Fullscreen + .GetAllFullscreen() + .FirstOrDefault(fullscreen => rootView ? + fullscreen.ActualViewPyramid.View == pyramid.View : + fullscreen.ActualViewPyramid.Container == pyramid.Container + ); + } + + /// Create a for a given window. + /// The window that will go fullscreen. If null a new one will be instantiated based on the given type. + /// The type of the window to instantiate if the given window is null. + /// Returns the newly created . + public static FullscreenWindow MakeFullscreen(T window = null) where T : EditorWindow { + return MakeFullscreen(typeof(T), window); + } + + /// Create a for a given window. + /// The type of the window to instantiate if the given window is null. + /// The window that will go fullscreen. If null a new one will be instantiated based on the given type. + /// Set this to true when the target window was created solely for fullscreen, + /// this will cause it to be destroyed once the fullscreen closes, it has no effects if the target window is null. + /// Returns the newly created . + public static FullscreenWindow MakeFullscreen(Type type, EditorWindow window = null, bool disposableWindow = false) { + var rect = FullscreenRects.GetFullscreenRect(FullscreenPreferences.RectSource, window); + var fullscreen = ScriptableObject.CreateInstance(); + + fullscreen.OpenWindow(rect, type, window, disposableWindow); + return fullscreen; + } + + /// Create a for a given view. + /// The view that will go fullscreen, cannot be null. + /// Returns the newly created . + public static FullscreenView MakeFullscreen(ScriptableObject view) { + if(!view) + throw new ArgumentNullException("view"); + + view.EnsureOfType(Types.View); + + var rect = FullscreenRects.GetFullscreenRect(FullscreenPreferences.RectSource, view); + var fullscreen = ScriptableObject.CreateInstance(); + + fullscreen.OpenView(rect, view); + + return fullscreen; + } + + /// Open a new fullscreen if there's none open, otherwise, close the one already open. + /// The window that will go fullscreen. If null a new one will be instantiated based on the given type. + /// The type of the window to instantiate if the given window is null. + public static void ToggleFullscreen(T window = null) where T : EditorWindow { + ToggleFullscreen(typeof(T), window); + } + + /// Open a new fullscreen if there's none open, otherwise, close the one already open. + /// The window that will go fullscreen. If null a new one will be instantiated based on the given type. + /// The type of the window to instantiate if the given window is null. + public static void ToggleFullscreen(Type type, EditorWindow window = null) { + var rect = FullscreenRects.GetFullscreenRect(FullscreenPreferences.RectSource, window); + var oldFullscreen = GetFullscreenFromView(window); + + if(oldFullscreen) { + oldFullscreen.Close(); + return; + } + + oldFullscreen = GetFullscreenOnRect(rect); + + var newFullscreen = MakeFullscreen(type, window); + + newFullscreen.didPresent += () => { + if(oldFullscreen) + oldFullscreen.Close(); + }; + } + + /// Open a new fullscreen if there's none open, otherwise, close the one already open. + /// The view that will go fullscreen, cannot be null. + public static void ToggleFullscreen(ScriptableObject view) { + var rect = FullscreenRects.GetFullscreenRect(FullscreenPreferences.RectSource, view); + var oldFullscreen = GetFullscreenFromView(view); + + if(oldFullscreen) { + oldFullscreen.Close(); + return; + } + + oldFullscreen = GetFullscreenOnRect(rect); + + var newFullscreen = MakeFullscreen(view); + + newFullscreen.didPresent += () => { + if(oldFullscreen) + oldFullscreen.Close(); + }; + } + + } +} diff --git a/Assets/Fullscreen/Editor/Fullscreen.cs.meta b/Assets/Fullscreen/Editor/Fullscreen.cs.meta new file mode 100644 index 0000000..07c9997 --- /dev/null +++ b/Assets/Fullscreen/Editor/Fullscreen.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 02b2fea24f2e74d40b0ea966449271f6 +timeCreated: 1508987359 +licenseType: Store +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/FullscreenCallbacks.cs b/Assets/Fullscreen/Editor/FullscreenCallbacks.cs new file mode 100644 index 0000000..62ec124 --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenCallbacks.cs @@ -0,0 +1,32 @@ +using System; + +namespace FullscreenEditor { + /// + /// Utility callbacks for fullscreen state changes. + /// + public static class FullscreenCallbacks { + + /// + /// Callback called before the views are restored to their original position. + /// + public static Action beforeFullscreenClose = (f) => { }; + + /// + /// Callback called before the container for the fullscreen view is created and + /// the views are moved between ContainerWindows. + /// + public static Action beforeFullscreenOpen = (f) => { }; + + /// + /// Callback called in the OnDestroy method of the FullscreenContainer, after the + /// views have been reverted to their orignal positions. + /// + public static Action afterFullscreenClose = (f) => { }; + + /// + /// Callback called after the fullscreen is opened and everything is already set up. + /// + public static Action afterFullscreenOpen = (f) => { }; + + } +} diff --git a/Assets/Fullscreen/Editor/FullscreenCallbacks.cs.meta b/Assets/Fullscreen/Editor/FullscreenCallbacks.cs.meta new file mode 100644 index 0000000..ae295d3 --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenCallbacks.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cce9fa341a0d9e743960c6283b72e4d8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/FullscreenContainer.cs b/Assets/Fullscreen/Editor/FullscreenContainer.cs new file mode 100644 index 0000000..ba2bba6 --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenContainer.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityObject = UnityEngine.Object; +using HostView = UnityEngine.ScriptableObject; +using View = UnityEngine.ScriptableObject; +using ContainerWindow = UnityEngine.ScriptableObject; + +namespace FullscreenEditor { + /// Manages the WindowContainers, Views and Windows that will be fullscreened. + public abstract partial class FullscreenContainer : ScriptableObject { + + [SerializeField] private int m_ourIndex = -1; + [SerializeField] private bool m_old = false; + + public Action didPresent = () => Logger.Debug("'Did Present' called"); + + private static int CurrentIndex { + get { return EditorPrefs.GetInt("FullscreenIdx", 0); } + set { EditorPrefs.SetInt("FullscreenIdx", value); } + } + + /// The true view pyramid of this fullscreen container. + public ViewPyramid ActualViewPyramid { + get { return new ViewPyramid(m_dst.Container); } + } + + /// The view that is currently fullscreened. + public View FullscreenedView { + get { return ActualViewPyramid.View; } + } + + /// Position and size of the WindowContainer created for this fullscreen. + public Rect Rect { + get { + return m_dst.Container ? + m_dst.Container.GetPropertyValue("position") : + new Rect(); + } + set { + if (m_dst.Container) { + m_dst.Container.InvokeMethod("SetMinMaxSizes", value.size, value.size); + m_dst.Container.SetPropertyValue("position", value); + Logger.Debug("Set {0} rect to {1}", this.name, value); + } else + Logger.Debug("No container on {0}, rect will not be set", this.name); + } + } + + private void Update() { + if (!m_dst.Container) + Close(); // Forcefully closed + } + + protected virtual void OnEnable() { + + if (m_ourIndex == -1) { + m_ourIndex = CurrentIndex++; + name = string.Format("Fullscreen #{0}", m_ourIndex); + hideFlags = HideFlags.HideAndDontSave; + } + + #if UNITY_2018_1_OR_NEWER + EditorApplication.wantsToQuit += WantsToQuit; + #endif + + if (m_old && !m_dst.Container) { + Logger.Warning("{0} wasn't properly closed", name); + // After 1 frame to prevent OnDisable and OnDestroy from being called before this methods returns + After.Frames(1, () => DestroyImmediate(this, true)); + } + + m_old = true; + EditorApplication.update += Update; + } + + protected virtual void OnDisable() { + EditorApplication.update -= Update; + #if UNITY_2018_1_OR_NEWER + EditorApplication.wantsToQuit += WantsToQuit; + #endif + } + + protected virtual void OnDestroy() { + Logger.Debug(name + " destroyed"); + + if (m_dst.Container) { + m_dst.Container.InvokeMethod("Close"); + Logger.Warning("Destroying {0} which has open containers, always close the fullscreen before destroying it", name); + } + + FullscreenCallbacks.afterFullscreenClose(this); + } + + /// Destroy this container and exit fullscreen. + public virtual void Close() { + + FullscreenCallbacks.beforeFullscreenClose(this); + + if (!m_dst.Window && m_dst.Container) + Logger.Error("Placeholder window has been closed, Fullscreen Editor won't be able to restore window position"); + + if (m_dst.Container) // Container may have been destroyed by Alt+F4 + m_dst.Container.InvokeMethod("Close"); // Closes the container, all its views and the windows + + DestroyImmediate(this, true); + + } + + /// Focus the view of this fullscreen. + public virtual void Focus() { + if (FullscreenedView && FullscreenedView.IsOfType(Types.GUIView)) + FullscreenUtility.FocusView(FullscreenedView); + } + + /// Gets wheter the view of this fullscreen is focused or not. + public virtual bool IsFocused() { + return FullscreenUtility.IsViewFocused(FullscreenedView); + } + + #if UNITY_2018_1_OR_NEWER + private bool WantsToQuit() { + // Close the fullscreen before closing the editor, this way we have a better + // ensurance that the fullscreen container will not be saved to the layout. + // ContainerWindow.m_DontSaveToLayout is set to true, so in Unity < 2018.1 the + // fullscreen will behave the same as if it was closed by Alt+F4. + Close(); + return true; + } + #endif + + } +} diff --git a/Assets/Fullscreen/Editor/FullscreenContainer.cs.meta b/Assets/Fullscreen/Editor/FullscreenContainer.cs.meta new file mode 100644 index 0000000..69feb93 --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenContainer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d4d34183a65705a41a116cf6e52733a6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/FullscreenContainerInternal.cs b/Assets/Fullscreen/Editor/FullscreenContainerInternal.cs new file mode 100644 index 0000000..5225230 --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenContainerInternal.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityObject = UnityEngine.Object; +using HostView = UnityEngine.ScriptableObject; +using View = UnityEngine.ScriptableObject; +using ContainerWindow = UnityEngine.ScriptableObject; + +namespace FullscreenEditor { + /// Manages the WindowContainers, Views and Windows that will be fullscreened. + public abstract partial class FullscreenContainer : ScriptableObject { + + /// The m_src will have all it's fields null if we've instantiated the window by ourselves. + /// Window - The window to fullscreen, null if we're opening a view. + /// View - The view to fullscreen, or window.m_Parent if we're opening a window. + /// Container - The source container, from the source view. + /// + [SerializeField] public ViewPyramid m_src; + + /// + /// Window - The placeholder window or a window created specially for this fullscreen. + /// View - HostView created for this fullscreen. + /// Container - The container that will be used for fullscreen, with its mode set to "Popup". + /// + [SerializeField] public ViewPyramid m_dst; + + /// Create a ContainerWindow and HostView, then asigns a window to them and shows it as a popup. + /// The initial position of the ContainerWindow, can be changed later using the property. + /// The initial window for the newly created ContainerWindow and HostView. + /// Returns the pyramid of views we've created. + protected ViewPyramid CreateFullscreenViewPyramid(Rect rect, EditorWindow childWindow) { + var hv = CreateInstance(Types.HostView); + var cw = CreateInstance(Types.ContainerWindow); + + hv.name = name; + cw.name = name; + childWindow.name = name; + + hv.SetPropertyValue("actualView", childWindow); + + // Order is important here: first set rect of container, then assign main view, then apply various settings, then show. + // Otherwise the rect won't be set until first resize happens. + cw.SetPropertyValue("position", rect); + cw.SetPropertyValue("rootView", hv); + + childWindow.InvokeMethod("MakeParentsSettingsMatchMe"); + + var loadPosition = false; + var displayImmediately = true; + var setFocus = true; + + if (cw.HasMethod("Show", new[] { typeof(int), typeof(bool), typeof(bool), typeof(bool), typeof(int) })) + cw.InvokeMethod("Show", (int)ShowMode.NoShadow, loadPosition, displayImmediately, setFocus, 0); + else if (cw.HasMethod("Show", new[] { typeof(int), typeof(bool), typeof(bool) })) + cw.InvokeMethod("Show", (int)ShowMode.NoShadow, loadPosition, displayImmediately); + else + cw.InvokeMethod("Show", (int)ShowMode.NoShadow, loadPosition, displayImmediately, setFocus); + + // set min/max size now that native window is not null so that it will e.g., use proper styleMask on macOS + cw.InvokeMethod("SetMinMaxSizes", rect.size, rect.size); // min, max + + cw.SetFieldValue("m_ShowMode", (int)ShowMode.PopupMenu); // Prevents window decoration from being draw + cw.SetFieldValue("m_DontSaveToLayout", true); + + Logger.Debug(this, "Created {0}, resolution {1:0}x{2:0}, pos {3:0}", name, rect.width, rect.height, rect.min); + + return new ViewPyramid() { Window = childWindow, View = hv, Container = cw }; + + } + + /// Prevents any repaint on the container window. This fixes some glitches on macOS. + /// The ContainerWindow to freeze the repaints. + /// Wheter to freeze or unfreeze the container. + protected void SetFreezeContainer(ContainerWindow containerWindow, bool freeze) { + containerWindow.InvokeMethod("SetFreezeDisplay", freeze); + } + + /// Method that will be called just before creating the ContainerWindow for this fullscreen. + protected virtual void BeforeOpening() { + + FullscreenCallbacks.beforeFullscreenOpen(this); + + if (m_dst.Container) + new Exception("Container already has a fullscreened view"); + } + + /// Method that will be called after the creation of the ContainerWindow for this fullscreen. + protected virtual void AfterOpening() { + + After.Frames(2, () => { + UnityEditorInternal.InternalEditorUtility.RepaintAllViews(); + + didPresent.Invoke(); + didPresent = null; + }); + + Logger.Debug(this, "{6}\n\nSRC\nWindow: {0}\nView: {1}\nContainer: {2}\n\nDST\nWindow: {3}\nView: {4}\nContainer: {5}\n", + m_src.Window, + m_src.View, + m_src.Container, + m_dst.Window, + m_dst.View, + m_dst.Container, + name + ); + + FullscreenCallbacks.afterFullscreenOpen(this); + } + + } + +} diff --git a/Assets/Fullscreen/Editor/FullscreenContainerInternal.cs.meta b/Assets/Fullscreen/Editor/FullscreenContainerInternal.cs.meta new file mode 100644 index 0000000..7526b32 --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenContainerInternal.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bba1c99b7a0742446947224d1c0e961e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/FullscreenOnPlay.cs b/Assets/Fullscreen/Editor/FullscreenOnPlay.cs new file mode 100644 index 0000000..6cadd68 --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenOnPlay.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace FullscreenEditor { + /// Toggle fullscreen upon playmode change if is set to true. + [InitializeOnLoad] + internal static class FullscreenOnPlay { + + static FullscreenOnPlay() { + +#if UNITY_2017_2_OR_NEWER + EditorApplication.playModeStateChanged += state => { + switch(state) { + case PlayModeStateChange.ExitingEditMode: + SetIsPlaying(true); + break; + + case PlayModeStateChange.ExitingPlayMode: + SetIsPlaying(false); + break; + + case PlayModeStateChange.EnteredPlayMode: + foreach(var fs in Fullscreen.GetAllFullscreen()) + if(fs && fs is FullscreenWindow && (fs as FullscreenWindow).CreatedByFullscreenOnPlay) { + FixGameViewMouseInput.UpdateGameViewArea(fs); + } + break; + } + }; + + EditorApplication.pauseStateChanged += state => SetIsPlaying(EditorApplication.isPlayingOrWillChangePlaymode && state == PauseState.Unpaused); +#else + EditorApplication.playmodeStateChanged += () => SetIsPlaying(EditorApplication.isPlayingOrWillChangePlaymode && !EditorApplication.isPaused); +#endif + + } + + private static void SetIsPlaying(bool playing) { + + var fullscreens = Fullscreen.GetAllFullscreen() + .Select(fullscreen => fullscreen as FullscreenWindow) + .Where(fullscreen => fullscreen); + + // We close all the game views created on play, even if the option was disabled in the middle of the play mode + // This is done to best reproduce the default behaviour of the maximize on play + if(!playing) { + foreach(var fs in fullscreens) + if(fs && fs.CreatedByFullscreenOnPlay) // fs might have been destroyed + fs.Close(); + return; + } + + if(!FullscreenPreferences.FullscreenOnPlayEnabled) + return; // Nothing to do here + + if(FullscreenUtility + .GetGameViews() + .Any(gv => { + if(!gv) return false; + + return PlaymodeBehaviourImplemented(gv) && GetEnterPlayModeBehavior(gv) == CustomEnterPlayModeBehavior.PlayFullscreen; + })) { + EditorUtility.DisplayDialog("Fullscreen on play conflict", "Seems like you have both Unity's built-in fullscreen on play and Fullscreen Editor's plugin enabled. Please, make sure you only have one of them enabled to prevent conflicts.", "Got it!"); + FullscreenPreferences.FullscreenOnPlayEnabled.Value = false; + return; + } + + var gameView = FullscreenOnPlayGameView(); + + if(!gameView) // no gameview has the fullscreen on play option enabled + return; + + foreach(var fs in fullscreens) + if(fs && fs.Rect.Overlaps(gameView.position)) // fs might have been destroyed + return; // We have an open fullscreen where the new one would be, so let it there + + if(gameView && Fullscreen.GetFullscreenFromView(gameView)) + return; // The gameview is already in fullscreen + + var gvfs = Fullscreen.MakeFullscreen(Types.GameView, gameView); + gvfs.CreatedByFullscreenOnPlay = true; + } + + internal static EditorWindow FullscreenOnPlayGameView() { + var mainGv = FullscreenUtility.GetMainGameView(); + + if(mainGv) return mainGv; + + return FullscreenUtility + .GetGameViews() + .FirstOrDefault(gv => gv); + } + + internal enum CustomEnterPlayModeBehavior { + PlayFocused, + PlayMaximized, + PlayUnfocused, + PlayFullscreen + } + + internal static bool PlaymodeBehaviourImplemented(EditorWindow playmodeView) { + return playmodeView.HasProperty("enterPlayModeBehavior"); + } + + internal static CustomEnterPlayModeBehavior GetEnterPlayModeBehavior(EditorWindow playmodeView) { + return (CustomEnterPlayModeBehavior)playmodeView.GetPropertyValue("enterPlayModeBehavior"); + } + + internal static void SetEnterPlayModeBehavior(EditorWindow playmodeView, CustomEnterPlayModeBehavior behaviour) { + if(playmodeView.HasProperty("playModeBehaviorIdx")) + playmodeView.SetPropertyValue("playModeBehaviorIdx", (int)behaviour); // unity forgot to update this prop when updating the play mode behaviour + playmodeView.SetPropertyValue("enterPlayModeBehavior", (int)behaviour); + } + + } +} diff --git a/Assets/Fullscreen/Editor/FullscreenOnPlay.cs.meta b/Assets/Fullscreen/Editor/FullscreenOnPlay.cs.meta new file mode 100644 index 0000000..6a68a88 --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenOnPlay.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: e246db13c913afe4aa7ca3064f3ee755 +timeCreated: 1509071892 +licenseType: Store +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/FullscreenPreferences.cs b/Assets/Fullscreen/Editor/FullscreenPreferences.cs new file mode 100644 index 0000000..106c66b --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenPreferences.cs @@ -0,0 +1,409 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace FullscreenEditor { + + /// Define a source mode to get a fullscreen rect. + public enum RectSourceMode { + /// The bounds of the main display. + MainDisplay, + /// Open on the display that the target window is located. + WindowDisplay, + /// The bounds of the display where the mouse pointer is. + AtMousePosition, + /// A rect that spans across all the displays. (Windows only) + Span, + /// A custom rect defined by . + Custom, + Display1 = 0x100, + Display2 = 0x101, + Display3 = 0x102, + Display4 = 0x103, + Display5 = 0x104, + Display6 = 0x105, + Display7 = 0x106, + Display8 = 0x107 + } + + /// Contains preferences for the Fullscreen Editor plugin. + [InitializeOnLoad] + public static class FullscreenPreferences { + + private const float LABEL_WIDTH = 300f; + private const string DEVELOPER_EMAIL = "support@mukaschultze.dev"; + private const string ASSET_STORE_PAGE = "https://assetstore.unity.com/packages/tools/utilities/fullscreen-editor-69534"; + private const string CHANGE_LOG_LINK = ASSET_STORE_PAGE + "#releases"; + private const string REVIEWS_LINK = ASSET_STORE_PAGE + "#reviews"; + private const string FORUM_THREAD = "https://forum.unity.com/threads/released-fullscreen-editor.661519/"; + + /// Current version of the Fullscreen Editor plugin. + public static readonly Version pluginVersion = new Version(2, 2, 8); + /// Release date of this version. + public static readonly DateTime pluginDate = new DateTime(2023, 09, 07); + + private static readonly GUIContent resetSettingsContent = new GUIContent("Use Defaults", "Reset all settings to default ones"); + private static readonly GUIContent versionContent = new GUIContent(string.Format("Version: {0} ({1:d})", pluginVersion, pluginDate)); + + private static readonly GUIContent[] links = new GUIContent[] { + // new GUIContent("Store Page", ASSET_STORE_PAGE), + new GUIContent("Forum Thread", FORUM_THREAD), + new GUIContent("Email Contact", GetEmailURL()), + new GUIContent("Changelog", CHANGE_LOG_LINK), + new GUIContent("Readme", GetFilePath("Readme.pdf")), + }; + + private static readonly string[] mosaicDropDownOptions = new[] { + "nothing", + "virtual display 1", + "virtual display 2", + "virtual display 3", + "virtual display 4", + "virtual display 5", + "virtual display 6", + "virtual display 7", + "virtual display 8", + }; + + internal static Action onLoadDefaults = () => { }; + internal static readonly List contents = new List(); + + private static readonly PrefItem scroll = new PrefItem("Scroll", Vector2.zero, string.Empty, string.Empty); + + /// Is the window toolbar currently visible? + public static readonly PrefItem ToolbarVisible; + + /// Is Fullscreen on Play currently enabled? + public static readonly PrefItem FullscreenOnPlayEnabled; + + /// Defines a source to get a fullscreen rect. + public static readonly PrefItem RectSource; + + /// Custom rect to be used when is set to . + public static readonly PrefItem CustomRect; + + /// Disable notifications when opening fullscreen windows. + public static readonly PrefItem DisableNotifications; + + /// Keep fullscreen views below other modal and utility windows. + public static readonly PrefItem KeepFullscreenBelow; + + /// Disable background SceneView rendering when there are open fullscreen views to increase performance. + public static readonly PrefItem DisableSceneViewRendering; + + /// Hide toolbars of all windows, not only the fullscreened (issue #80). + public static readonly PrefItem UseGlobalToolbarHiding; + + /// Defines which display renders on each screen when using Mosaic. + public static readonly PrefItem MosaicMapping; + + /// Do not attempt to use wmctrl's fullscreen on Linux environments. + public static readonly PrefItem DoNotUseWmctrl; + + /// Restore cursor lock and hide state after going in and out of fullscreen. + public static readonly PrefItem RestoreCursorLockAndHideState; + + [RuntimeInitializeOnLoadMethod] + private static void SyncVersion() { // Sync for automation + PlayerPrefs.SetString("PLUGIN_VERSION", pluginVersion.ToString()); + } + + static FullscreenPreferences() { + var rectSourceTooltip = string.Empty; + + rectSourceTooltip += "Controls where Fullscreen views opens.\n\n"; + rectSourceTooltip += "Main Screen: Fullscreen opens on the primary screen;\n\n"; + rectSourceTooltip += "Window Display: Open on the display that the target window is located (Windows only);\n\n"; + rectSourceTooltip += "At Mouse Position: Fullscreen opens on the screen where the mouse pointer is;\n\n"; + rectSourceTooltip += "Span: Fullscreen spans across all screens (Windows only);\n\n"; + rectSourceTooltip += "Custom Rect: Fullscreen opens on the given custom Rect."; + + ToolbarVisible = new PrefItem("Toolbar", false, "Toolbar Visible", "Show and hide the toolbar on the top of some windows, like the Game View and Scene View."); + FullscreenOnPlayEnabled = new PrefItem("FullscreenOnPlay", false, "Fullscreen On Play", "Override the \"Maximize on Play\" option of the game view to \"Fullscreen on Play\""); + RectSource = new PrefItem("RectSource", RectSourceMode.MainDisplay, "Placement source", rectSourceTooltip); + CustomRect = new PrefItem("CustomRect", FullscreenRects.GetMainDisplayRect(), "Custom Rect", string.Empty); + DisableNotifications = new PrefItem("DisableNotifications", false, "Disable Notifications", "Disable the notifications that shows up when opening a new fullscreen view."); + KeepFullscreenBelow = new PrefItem("KeepFullscreenBelow", true, "Keep Utility Views Above", "Keep utility views on top of fullscreen views.\nThis is useful to integrate with assets that need to keep windows open, such as Peek by Ludiq."); + DisableSceneViewRendering = new PrefItem("DisableSceneViewRendering", true, "Disable Scene View Rendering", "Increase Fullscreen Editor performance by not rendering SceneViews while there are open fullscreen views."); + UseGlobalToolbarHiding = new PrefItem("UseGlobalToolbarHiding", FullscreenUtility.IsMacOS, "Use global toolbar hiding", "Changes toolbars of all windows at once. This option fixes the gray bar bug on MacOS."); + MosaicMapping = new PrefItem("MosaicMapping", new[] { 0, 1, 2, 3, 4, 5, 6, 7 }, "Mosaic Screen Mapping", "Defines which display renders on each screen when using Mosaic."); + DoNotUseWmctrl = new PrefItem("DoNotUseWmctrl", false, "Do not use wmctrl", "Avoid using 'wmctrl' helper when opening fullscreen windows"); + RestoreCursorLockAndHideState = new PrefItem("RestoreCursorLockAndHideState", true, "Restore Cursor Lock and Hide State", "Restore cursor lock and hide state after going in and out of fullscreen."); + + onLoadDefaults += () => // Array won't revert automaticaly because it is changed as reference + MosaicMapping.Value = new[] { 0, 1, 2, 3, 4, 5, 6, 7 }; + + if (FullscreenUtility.MenuItemHasShortcut(Shortcut.TOOLBAR_PATH)) + ToolbarVisible.Content.text += string.Format(" ({0})", FullscreenUtility.TextifyMenuItemShortcut(Shortcut.TOOLBAR_PATH)); + if (FullscreenUtility.MenuItemHasShortcut(Shortcut.FULLSCREEN_ON_PLAY_PATH)) + FullscreenOnPlayEnabled.Content.text += string.Format(" ({0})", FullscreenUtility.TextifyMenuItemShortcut(Shortcut.FULLSCREEN_ON_PLAY_PATH)); + } + +#if UNITY_2018_3_OR_NEWER + [SettingsProvider] + private static SettingsProvider RetrieveSettingsProvider() { + var sp = new SettingsProvider("Preferences/Fullscreen Editor", SettingsScope.User, contents.Select(c => c.text)); + sp.footerBarGuiHandler = OnFooterGUI; + sp.guiHandler = (search) => { + EditorGUIUtility.labelWidth = LABEL_WIDTH; + OnPreferencesGUI(search); + }; + return sp; + } + + [SettingsProvider] + private static SettingsProvider RetrieveSettingsProviderShortcuts() { + var sp = new SettingsProvider("Preferences/Fullscreen Editor/Shortcuts", SettingsScope.User, contents.Select(c => c.text)); + sp.footerBarGuiHandler = OnFooterGUI; + sp.guiHandler = (search) => { + EditorGUIUtility.labelWidth = LABEL_WIDTH; + Shortcut.DoShortcutsGUI(); + }; + return sp; + } + +#else + [PreferenceItem("Fullscreen")] + private static void OnPreferencesGUI() { + scroll.Value = EditorGUILayout.BeginScrollView(scroll); + OnPreferencesGUI(string.Empty); + EditorGUILayout.Separator(); + + EditorGUILayout.LabelField("Shortcuts", EditorStyles.boldLabel); + EditorGUI.indentLevel++; + Shortcut.DoShortcutsGUI(); + EditorGUI.indentLevel--; + + EditorGUILayout.EndScrollView(); + OnFooterGUI(); + } +#endif + + private static void OnPreferencesGUI(string search) { + + ToolbarVisible.DoGUI(); + FullscreenOnPlayEnabled.DoGUI(); + + EditorGUILayout.Separator(); + RectSource.DoGUI(); + + if (RectSource.Value == RectSourceMode.AtMousePosition) + EditorGUILayout.HelpBox("\'At mouse position\' can cause slowdowns on large projects", MessageType.Warning); + + if (!IsRectModeSupported(RectSource)) + EditorGUILayout.HelpBox("The selected placement source mode is not supported on this platform", MessageType.Warning); + + // Custom Rect + switch (RectSource.Value) { + case RectSourceMode.Custom: + EditorGUI.indentLevel++; + CustomRect.DoGUI(); + + var customRect = CustomRect.Value; + + if (customRect.width < 300f) + customRect.width = 300f; + if (customRect.height < 300f) + customRect.height = 300f; + + CustomRect.Value = customRect; + + EditorGUI.indentLevel--; + break; + } + + DisableNotifications.DoGUI(); + KeepFullscreenBelow.DoGUI(); + + if (Patcher.IsSupported()) + DisableSceneViewRendering.DoGUI(); + + UseGlobalToolbarHiding.DoGUI(); + RestoreCursorLockAndHideState.DoGUI(); + + if (FullscreenUtility.IsLinux) { + using (new EditorGUI.DisabledGroupScope(!FullscreenEditor.Linux.wmctrl.IsInstalled)) { + DoNotUseWmctrl.DoGUI(); + } + if (!FullscreenEditor.Linux.wmctrl.IsInstalled) { + EditorGUILayout.HelpBox("'wmctrl' not found. Try installing it with 'sudo apt-get install wmctrl'.", MessageType.Warning); + } else { + EditorGUILayout.HelpBox("Try enabling the option above if you're experiencing any kind of toolbars or offsets " + + "while in fullscreen mode.\nDisabling 'wmctrl' can fix issues on some Linux environments when the window manager " + + "does not handle fullscreen windows properly (I'm looking at you Ubuntu).", MessageType.Info); + } + } + + // Mosaic + if (FullscreenRects.ScreenCount > 1) { + EditorGUILayout.Separator(); + EditorGUILayout.LabelField(MosaicMapping.Content, EditorStyles.boldLabel); + EditorGUI.indentLevel++; + GUI.changed = false; + var mosaicMapping = MosaicMapping.Value; + + for (var i = 0; i < mosaicMapping.Length && i < FullscreenRects.ScreenCount; i++) { + var val = EditorGUILayout.IntPopup(string.Format("Physical display {0} renders", i + 1), mosaicMapping[i], mosaicDropDownOptions, new[] { -1, 0, 1, 2, 3, 4, 5, 6, 7 }); + mosaicMapping[i] = val; + } + + if (GUI.changed) + MosaicMapping.SaveValue(); + EditorGUI.indentLevel--; + } + } + + private static void OnFooterGUI() { + + Func linkLabel = (label) => + typeof(EditorGUILayout).HasMethod("LinkLabel", new[] { typeof(string), typeof(GUILayoutOption[]) }) ? // Issue #100 + typeof(EditorGUILayout).InvokeMethod("LinkLabel", label, new GUILayoutOption[0]) : // < 2020.1 + typeof(EditorGUILayout).InvokeMethod("LinkButton", label, new GUILayoutOption[0]); // >= 2020.1 + ; + + using (new EditorGUILayout.HorizontalScope()) { + GUILayout.FlexibleSpace(); + if (linkLabel(new GUIContent("Consider leaving a review if you're enjoying Fullscreen Editor!", REVIEWS_LINK))) + Application.OpenURL(REVIEWS_LINK); + GUILayout.FlexibleSpace(); + } + + using (new EditorGUILayout.HorizontalScope()) { + GUILayout.FlexibleSpace(); + for (var i = 0; i < links.Length; i++) { + if (linkLabel(links[i])) + Application.OpenURL(links[i].tooltip); + GUILayout.Space(5f); + } + GUILayout.FlexibleSpace(); + } + + EditorGUILayout.Separator(); + + using (new EditorGUILayout.HorizontalScope()) { + if (GUILayout.Button(resetSettingsContent, GUILayout.Width(120f))) + onLoadDefaults(); + + using (new EditorGUI.DisabledGroupScope(EditorApplication.isCompiling)) { + GUI.changed = false; + var enable = GUILayout.Toggle(Integration.IsDirectiveDefined("FULLSCREEN_DEBUG"), "Debug", "Button"); + if (GUI.changed) { + Integration.SetDirectiveDefined("FULLSCREEN_DEBUG", enable); + } + } + + if (GUILayout.Button("Copy debug data", GUILayout.Width(120f))) + CopyDisplayDebugInfo(); + + GUILayout.FlexibleSpace(); + EditorGUILayout.LabelField(versionContent, GUILayout.Width(170f)); + } + + EditorGUILayout.Separator(); + } + + private static void CopyDisplayDebugInfo() { + var str = new StringBuilder(); + + str.Append("Fullscreen Editor"); + str.AppendFormat("\nVersion: {0}", pluginVersion.ToString(3)); + str.AppendFormat("\nUnity {0}", InternalEditorUtility.GetFullUnityVersion()); + str.AppendFormat("\n{0}", SystemInfo.operatingSystem); + str.AppendFormat("\nScaling {0:p}", FullscreenUtility.GetDisplayScaling()); + + foreach (var display in DisplayInfo.GetDisplays()) { + str.AppendFormat("\n----------- DISPLAY -----------"); + str.AppendFormat("\nDeviceName: {0} ({1})", display.FriendlyName, display.DeviceName); + str.AppendFormat("\nDpiCorrectedArea: {0}", display.DpiCorrectedArea); + str.AppendFormat("\nUnityCorrectedArea: {0}", display.UnityCorrectedArea); + str.AppendFormat("\nMonitorArea: {0}", display.MonitorArea); + str.AppendFormat("\nPhysicalArea: {0}", display.PhysicalArea); + str.AppendFormat("\nWorkArea: {0}", display.WorkArea); + str.AppendFormat("\nLogicalScreenHeight: {0}", display.LogicalScreenHeight); + str.AppendFormat("\nPhysicalScreenHeight: {0}", display.PhysicalScreenHeight); + str.AppendFormat("\nScreenWidth: {0}", display.ScreenWidth); + str.AppendFormat("\nScreenHeight: {0}", display.ScreenHeight); + str.AppendFormat("\nPrimaryDisplay: {0}", display.PrimaryDisplay); + str.AppendFormat("\nscaleFactor: {0}", display.scaleFactor); + str.AppendFormat("\nscaleFactor2: {0}", display.scaleFactor2); + str.AppendFormat("\ndevMode: {0}\n", JsonUtility.ToJson(display.devMode, true)); + } + + EditorGUIUtility.systemCopyBuffer = str.ToString(); + EditorUtility.DisplayDialog("Debug", "Display debug data was copied to the clipboard", "OK"); + } + + private static string GetFilePath(string file) { + var stack = new StackFrame(0, true); + var currentFile = stack.GetFileName(); + var currentPath = Path.GetDirectoryName(currentFile); + + return Path.Combine(currentPath, "../" + file); + } + + private static string GetEmailURL(Exception e = null) { + var full = new StringBuilder(); + var body = new StringBuilder(); + +#if UNITY_2018_1_OR_NEWER + Func EscapeURL = url => UnityEngine.Networking.UnityWebRequest.EscapeURL(url).Replace("+", "%20"); +#else + Func EscapeURL = url => WWW.EscapeURL(url).Replace("+", "%20"); +#endif + + body.Append("\nDescribe your issue or make your request here"); + body.Append("\n\nAdditional Information:"); + body.AppendFormat("\nVersion: {0}", pluginVersion.ToString(3)); + body.AppendFormat("\nUnity {0}", InternalEditorUtility.GetFullUnityVersion()); + body.AppendFormat("\n{0}", SystemInfo.operatingSystem); + + if (e != null) + body.AppendFormat("\n\nEXCEPTION\n", e); + + full.Append("mailto:"); + full.Append(DEVELOPER_EMAIL); + full.Append("?subject="); + full.Append(EscapeURL("Fullscreen Editor - Support")); + full.Append("&body="); + full.Append(EscapeURL(body.ToString())); + + return full.ToString(); + } + + internal static bool IsRectModeSupported(RectSourceMode mode) { + switch (mode) { + case RectSourceMode.Display1: + case RectSourceMode.Display2: + case RectSourceMode.Display3: + case RectSourceMode.Display4: + case RectSourceMode.Display5: + case RectSourceMode.Display6: + case RectSourceMode.Display7: + case RectSourceMode.Display8: + case RectSourceMode.Span: + case RectSourceMode.WindowDisplay: + return FullscreenUtility.IsWindows; + + case RectSourceMode.MainDisplay: + case RectSourceMode.AtMousePosition: + return true; + + case RectSourceMode.Custom: + // Custom rect is not supported on Linux + // since we're using native fullscreen + return !FullscreenUtility.IsLinux; + + default: + return false; + } + } + + } + +} diff --git a/Assets/Fullscreen/Editor/FullscreenPreferences.cs.meta b/Assets/Fullscreen/Editor/FullscreenPreferences.cs.meta new file mode 100644 index 0000000..cf4f8b0 --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenPreferences.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 75ef3c7b609ce8c409782a523b7ea64a +timeCreated: 1508376099 +licenseType: Store +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/FullscreenRects.cs b/Assets/Fullscreen/Editor/FullscreenRects.cs new file mode 100644 index 0000000..ec21ebc --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenRects.cs @@ -0,0 +1,193 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; +using Object = UnityEngine.Object; + +using FullscreenEditor.Windows; + +namespace FullscreenEditor { + /// Helper for getting fullscreen rectangles. + public static class FullscreenRects { + + /// Represents a callback for user defined fullscreen rect calculation. + /// The mode set in + /// A rect calculated based on custom logic. + /// Whether the rect calculated should be used or not. + public delegate bool FullscreenRectCallback(RectSourceMode mode, out Rect rect); + + /// The number of monitors attached to this machine, returns -1 if the platform is not supported. + public static int ScreenCount { + get { + if (!FullscreenUtility.IsWindows) + return -1; + const int SM_CMONITORS = 80; + return User32.GetSystemMetrics(SM_CMONITORS); + } + } + + /// Custom callback to allow the user to specify their own logic to how fullscreens will be arranged. + /// Check the documentation for usage examples. + public static FullscreenRectCallback CustomRectCallback { get; set; } + + /// Returns a fullscreen rect + /// The mode that will be used to retrieve the rect. + /// The window that will be set fullscreen. + public static Rect GetFullscreenRect(RectSourceMode mode, ScriptableObject targetWindow = null) { + + if (targetWindow != null && !targetWindow.IsOfType(typeof(EditorWindow)) && !targetWindow.IsOfType(Types.View)) { + throw new ArgumentException("Target window must be of type EditorWindow or View or null", "targetWindow"); + } + + if (CustomRectCallback != null) { + var rect = new Rect(); + var shouldUse = CustomRectCallback(mode, out rect); + + if (shouldUse) + return rect; + } + + switch (mode) { + case RectSourceMode.MainDisplay: + return GetMainDisplayRect(); + + case RectSourceMode.WindowDisplay: + if (targetWindow == null || !FullscreenUtility.IsWindows) + return GetMainDisplayRect(); + + var views = new ViewPyramid(targetWindow); + var rect = views.Container.GetPropertyValue("position"); + + return GetDisplayBoundsAtPoint(rect.center); + + case RectSourceMode.AtMousePosition: + return FullscreenUtility.IsWindows ? + GetDisplayBoundsAtPoint(FullscreenUtility.MousePosition) : + GetWorkAreaRect(true); + + case RectSourceMode.Span: + return FullscreenUtility.IsWindows ? + GetVirtualScreenBounds() : + GetWorkAreaRect(true); + + case RectSourceMode.Custom: + return GetCustomUserRect(); + + case RectSourceMode.Display1: + return GetMonitorRect(0); + case RectSourceMode.Display2: + return GetMonitorRect(1); + case RectSourceMode.Display3: + return GetMonitorRect(2); + case RectSourceMode.Display4: + return GetMonitorRect(3); + case RectSourceMode.Display5: + return GetMonitorRect(4); + case RectSourceMode.Display6: + return GetMonitorRect(5); + case RectSourceMode.Display7: + return GetMonitorRect(6); + case RectSourceMode.Display8: + return GetMonitorRect(7); + + default: + Logger.Warning("Invalid fullscreen mode, please fix this by changing the placement source mode in preferences."); + return new Rect(Vector2.zero, Vector2.one * 300f); + } + } + + /// Returns a rect with the dimensions of the main screen. + /// (Note that the position may not be right for multiple screen setups) + public static Rect GetMainDisplayRect() { + + if (FullscreenUtility.IsWindows) { + var mainDisplay = DisplayInfo + .GetDisplays() + .FirstOrDefault(d => d.PrimaryDisplay); + + if (mainDisplay != null) + return mainDisplay.UnityCorrectedArea; + + Logger.Error("No main display??? This should not happen, falling back to Screen.currentResolution"); + } + + // Screen.currentResolution returns the resolution of the screen where + // the currently focused window is located, not the main display resolution. + // This caused the bug #53 on windows. + // The same behaviour was not tested on Linux as macOS + return new Rect(0f, 0f, Screen.currentResolution.width, Screen.currentResolution.height); + } + + /// Returns the rect of a given display index. + public static Rect GetMonitorRect(int index) { + + if (!FullscreenUtility.IsWindows) + return GetMainDisplayRect(); + + var d = DisplayInfo.GetDisplay(index); + + if (d == null) { + Logger.Error("Display {0} not connected", index + 1); + return GetMainDisplayRect(); + } + + return d.UnityCorrectedArea; + } + + /// Returns a rect defined by the user in the preferences. + public static Rect GetCustomUserRect() { + return FullscreenPreferences.CustomRect; + } + + /// Returns a rect covering all the screen, except for the taskbar/dock. + /// On Windows it adds a 4px border and does not account for scaling (can cause bugs when using scales different than 100%). + /// On macOS this returns a fullscreen rect when the main window is maximized and mouseScreen is set to true. + /// Should we get the rect on the screen where the mouse pointer is? + public static Rect GetWorkAreaRect(bool mouseScreen) { + return Types.ContainerWindow.InvokeMethod("FitRectToScreen", new Rect(Vector2.zero, Vector2.one * 10000f), true, mouseScreen); + } + + /// Returns a rect covering all the screen, except for the taskbar/dock. + /// On Windows it adds a 4px border and does not account for scaling (can cause bugs when using scales different than 100%). + /// On macOS this returns a fullscreen rect when the main window is maximized and mouseScreen is set to true. + /// The ContainerWindow that will be used as reference for calulating border error. + /// Should we get the rect on the screen where the mouse pointer is? + public static Rect GetWorkAreaRect(Object container, bool mouseScreen) { + return container.InvokeMethod("FitWindowRectToScreen", new Rect(Vector2.zero, Vector2.one * 10000f), true, mouseScreen); + } + + /// Returns the bounds rect of the screen that contains the given point. (Windows only) + /// The point relative to + public static Rect GetDisplayBoundsAtPoint(Vector2 point) { + return InternalEditorUtility.GetBoundsOfDesktopAtPoint(point); + } + + /// Full virtual screen bounds, spanning across all monitors. (Windows only) + public static Rect GetVirtualScreenBounds() { + + if (!FullscreenUtility.IsWindows) + throw new NotImplementedException(); + + const int SM_XVIRTUALSCREEN = 76; + const int SM_YVIRTUALSCREEN = 77; + const int SM_CXVIRTUALSCREEN = 78; + const int SM_CYVIRTUALSCREEN = 79; + + var x = User32.GetSystemMetrics(SM_XVIRTUALSCREEN); + var y = User32.GetSystemMetrics(SM_YVIRTUALSCREEN); + var width = User32.GetSystemMetrics(SM_CXVIRTUALSCREEN); + var height = User32.GetSystemMetrics(SM_CYVIRTUALSCREEN); + + var rect = new Rect { + yMin = y, + xMin = x, + width = width, + height = height, + }; + + return FullscreenUtility.DpiCorrectedArea(rect); + } + + } +} diff --git a/Assets/Fullscreen/Editor/FullscreenRects.cs.meta b/Assets/Fullscreen/Editor/FullscreenRects.cs.meta new file mode 100644 index 0000000..17f1833 --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenRects.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 6b84448503834b34ab16a1e3be4d97fd +timeCreated: 1509127111 +licenseType: Store +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/FullscreenUtility.cs b/Assets/Fullscreen/Editor/FullscreenUtility.cs new file mode 100644 index 0000000..cbb69c8 --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenUtility.cs @@ -0,0 +1,432 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; +using UnityObject = UnityEngine.Object; + +namespace FullscreenEditor { + + /// Clone of the internal UnityEditor.ShowMode. + public enum ShowMode { + /// Show as a normal window with max, min & close buttons. + NormalWindow = 0, + /// Used for a popup menu. On mac this means light shadow and no titlebar. + PopupMenu = 1, + /// Utility window - floats above the app. Disappears when app loses focus. + Utility = 2, + /// Window has no shadow or decorations. Used internally for dragging stuff around. + NoShadow = 3, + /// The Unity main window. On mac, this is the same as NormalWindow, except window doesn't have a close button. + MainWindow = 4, + /// Aux windows. The ones that close the moment you move the mouse out of them. + AuxWindow = 5, + /// Like PopupMenu, but without keyboard focus. + Tooltip = 6, + // Show as fullscreen window + Fullscreen = 8 + } + + /// Helper class for suppressing unity logs when calling a method that may show unwanted logs. + internal class SuppressLog : IDisposable { + + private readonly bool lastState; + + internal static ILogger Logger { + get { +#if UNITY_2017_1_OR_NEWER + return Debug.unityLogger; +#else + return Debug.logger; +#endif + } + } + + public SuppressLog() { + lastState = Logger.logEnabled; + Logger.logEnabled = false; + } + + public void Dispose() { + Logger.logEnabled = lastState; + } + + } + + /// Miscellaneous utilities for Fullscreen Editor. + [InitializeOnLoad] + public static class FullscreenUtility { + + /// Contains a Texture2D icon loaded from a base64. + public class Icon { + + private string m_base64; + private Texture2D m_texture; + + public Texture2D Texture { + get { return m_texture ? m_texture : (m_texture = FindOrLoadTexture(m_base64)); } + } + + public Icon(string base64) { + m_base64 = base64; + } + + public Icon(string base64, string proVariantBase64) { + m_base64 = EditorGUIUtility.isProSkin ? proVariantBase64 : base64; + } + + public static implicit operator Texture2D(Icon icon) { + return icon.Texture; + } + + } + + private static Vector2 mousePosition; + + public static bool IsWindows { get { return Application.platform == RuntimePlatform.WindowsEditor; } } + public static bool IsMacOS { get { return Application.platform == RuntimePlatform.OSXEditor; } } + public static bool IsLinux { get { return Application.platform == RuntimePlatform.LinuxEditor; } } + + static FullscreenUtility() { + + var lastUpdate = EditorApplication.timeSinceStartup; + + EditorApplication.update += () => { + if(EditorApplication.timeSinceStartup - lastUpdate > 0.5f && FullscreenPreferences.RectSource.Value == RectSourceMode.AtMousePosition) { + EditorApplication.RepaintHierarchyWindow(); + EditorApplication.RepaintProjectWindow(); + lastUpdate = EditorApplication.timeSinceStartup; + } + }; + + EditorApplication.hierarchyWindowItemOnGUI += (rect, id) => RecalculateMousePosition(); + EditorApplication.projectWindowItemOnGUI += (rect, id) => RecalculateMousePosition(); + +#if UNITY_2019_1_OR_NEWER + SceneView.duringSceneGui += sceneView => RecalculateMousePosition(); +#else + SceneView.onSceneGUIDelegate += sceneView => RecalculateMousePosition(); +#endif + + } + + private static void RecalculateMousePosition() { + mousePosition = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); + } + + /// Returns wheter the extension is running with debugging enabled. + public static bool DebugModeEnabled { + get { +#if FULLSCREEN_DEBUG + return true; +#else + return false; +#endif + } + } + + /// The mouse position, can be called outside an OnGUI method. + public static Vector2 MousePosition { get { return mousePosition; } } + + /// The icon of this plugin. + public static readonly Icon FullscreenIcon = new Icon( + "iVBORw0KGgoAAAANSUhEUgAAAC4AAAAuCAMAAABgZ9sFAAAApVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+4/eNVAAAANnRSTlMA/AfHA2sLtBTy8MxYO/nk9u3Qj06wNCceGeCppF9LPhDot5iBewHYn1JDQS8sIyCJdkdluZF8FV+iAAAB6klEQVRIx+2SWbKbMBBFJcAYC8xswAMznme/pPe/tNASLgImVH5TeefLvhxaouuS/xTpHrwi0nJ/BZE04O2IeomrcqHnTL+RN4HLLH16qOJr/53YYiYI9pcmS/MmMdk+6OoTaLHOPErsNpqlPZ2KnIJpMzerk0x3bAq00efDujw5BuFNws+OwsCY7Md169SJfTaug/nVhsoWYFRHtkqTqZ4I6LA+e1j8qScJe8NHuB4M6+brtgJkreJmZPHty/hjkWLJpk9CmU/UJaLxk8y1RgwAcELSxbfA9gnR+A3WClGnAMAqlaC+OpE+FxcSvPGWwkbBHwXkxx1BXQ7JJ8upz1s5qS+ARAdRBvw/iDSYquSfRflDbCz4k6wyhGEUGl+Y7g9t5QlT1O4LyHlBjgAF7ltzaSx9LH1DAaenOpY+qGfPsF5pretgP3rL3G0AUDcsQOrjRdVZjDpA2bFFQ35ITxMdmmD0Jbo+yVDXezdHXcZmt31KHEA8Fx996G/283c4F+2nY7q8bNOry6MRfd3ZmVaM67OHMQ+jDHe1vF1T4ycd1SlQ07F8jLycmZQOTsdFthw0jKIFIMPTD8yGhlJpRpTvxHaKfsHux225kFcOq9rMc5yVvPGeSUR2Q6VcXk7z3888n68a+eav+AWMDWJNSUXp9QAAAABJRU5ErkJggg==" + ); + + /// A smaller icon of this plugin. + public static readonly Icon FullscreenIconSmall = new Icon( + "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAMAAAAMCGV4AAAAclBMVEUAAABmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmbn+Ue6AAAAJXRSTlMABfscDPdX3LmNgF9IKxPtoXdvQxiumWlTJPLjkYcx6M3KxrM+6Owz4AAAAKJJREFUCNdlzlkOwyAMBNDBkECALDRkb9LV979infazliz5eX4GS/Cd6wFE1/mQcWHm4qmhH4Vc5WmaGwXVBPq5pTYCmFdqxRNfPFOJwJx2sX55pK3CaCdUQwZMhtJKFtAG/5PVNzeS1wo47rWqbG+cLUHJoOLD8SY/pnijN67csl0A5bkoqBfzuki/xnTM4vHs3xlEe/aPqKd9GGoAwY1pbj58lQvfBIytyAAAAABJRU5ErkJggg==", + "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAMAAAAMCGV4AAAAclBMVEUAAAC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSyGfY5AAAAJXRSTlMABfsM91ccGNy5jYBfSCsT7aF3b0OumWlTJPLjkYcx6M3KxrM+ZTSY8wAAAKJJREFUCNdljtsSgyAMRJegAgIqeNdqr/n/X2ycPjYzO5Oz+3KwBp/cACC65INBz8zFy6B6FvKVF9PSKqg20I876iKAZaNOeObeM5UIzPkQrt4eea8x2Rn1aABtoColASqN/zOySbTsjQLOR6NqO2hnS1DWqPl0vEvHFO/0wY07tiugPBcFDcK8reLX6sQsPF3+SSPayz+imY9xbAAEN+Wl/QKAZAvuNVnEigAAAABJRU5ErkJggg==" + ); + + /// The icon to show on the game view toolbar when fullscreen on play is enabled. + public static readonly Icon FullscreenOnPlayIcon = new Icon( + "iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAMAAAAMs7fIAAAAZlBMVEUAAAAZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRnaqT0eAAAAIXRSTlMA9rSVTunBWTwgFNzOrvvYo4x1ZGA4MALTx6eae3dTCgkQ40nLAAAAcklEQVQY053OORaFIBBE0WpsBQSchz+qvf9NSmCHJlZ4g3cKj8ddbD3q6pWmSwqRnjH/SIxKQ1WN4yOkMpSuDKuhLX41ZOEt3IJxxe0mLA7Ww0LLb87NkGvDJYYk7fkPNYWK0H8G9yIqY2rzHx9ix3i6E/A3BM5M0CXHAAAAAElFTkSuQmCC", + "iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAMAAAAMs7fIAAAAY1BMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+aRQ2gAAAAIHRSTlMA9rSV6cFZPCAU3M6uT/vYo4x1ZGBMODAJ08enmnt3U4mFYqQAAABvSURBVBjTnc43AoAwDATBkwHbOJBz1P9fiQvUUqByitXh98XG1xZlsYbhlYy5HTGdxEqkoqLEsjOJdLnJHRTd/pCQhtUwM/qPZwNmA22hIeUtpqZLte4VRRyWtIeqTITpmjC2zCJ9qNMe63wT8fce2Z8EsL04YKYAAAAASUVORK5CYII=" + ); + + /// Show the notification of a newly opened about using the shortcut to close. + /// The fullscreen window. + /// The path containing a shortcut. + internal static void ShowFullscreenExitNotification(EditorWindow window, string menuItemPath) { + if(FullscreenPreferences.DisableNotifications) + return; + + var notification = string.Format("Press {0} to exit fullscreen", TextifyMenuItemShortcut(menuItemPath)); + ShowFullscreenNotification(window, notification); + } + + /// Show a fullscreen notification in an . + /// The host of the notification. + /// The message to show. + public static void ShowFullscreenNotification(EditorWindow window, string message) { + if(!window) + return; + + window.ShowNotification(new GUIContent(message, FullscreenIcon)); + window.Repaint(); + + if(EditorWindow.mouseOverWindow) // This definitely made sense when I made it, so I won't remove + EditorWindow.mouseOverWindow.Repaint(); + } + + /// Does the given path contains a key binding? + public static bool MenuItemHasShortcut(string menuItemPath) { + var index = menuItemPath.LastIndexOf(" "); + + if(index++ == -1) + return false; + + var shortcut = menuItemPath.Substring(index).Replace("_", ""); + var evt = Event.KeyboardEvent(shortcut); + + shortcut = InternalEditorUtility.TextifyEvent(evt); + + return !shortcut.Equals("None", StringComparison.InvariantCultureIgnoreCase); + } + + /// Gets a human-readable shortcut. + /// The path containing a shortcut. + /// + public static string TextifyMenuItemShortcut(string menuItemPath) { + var index = menuItemPath.LastIndexOf(" "); + + if(index++ == -1) + return "None"; + + var shortcut = menuItemPath.Substring(index).Replace("_", ""); + var evt = Event.KeyboardEvent(shortcut); + + shortcut = InternalEditorUtility.TextifyEvent(evt); + + return shortcut; + } + + private static Texture2D FindOrLoadTexture(string base64) { + var found = GetRef(base64); + + return found ? + found : + LoadTexture(base64); + } + + private static Texture2D LoadTexture(string base64) { + try { + var texture = new Texture2D(1, 1, TextureFormat.ARGB32, false, true); + var bytes = Convert.FromBase64String(base64); + + texture.name = base64; + texture.hideFlags = HideFlags.HideAndDontSave; + texture.LoadImage(bytes); + + return texture; + } catch(Exception e) { + Logger.Error("Failed to load texture: {0}", e); + return null; + } + } + + /// Find an object by it's name and type. + /// The name of the object to search for. + /// The type of the object to search for. + public static T GetRef(string name) where T : UnityObject { + return Resources.FindObjectsOfTypeAll().FirstOrDefault(obj => obj.name == name); + } + + /// Find an object by it's name and type. + /// The name of the object to search for. + /// The type of the object to search for. + public static UnityObject GetRef(Type type, string name) { + return Resources.FindObjectsOfTypeAll(type).FirstOrDefault(obj => obj.name == name); + } + + /// Get the main view. + public static ScriptableObject GetMainView() { + var containers = Resources.FindObjectsOfTypeAll(Types.MainView); + + if(containers.Length > 0) + return containers[0] as ScriptableObject; + + throw new Exception("Couldn't find main view"); + } + + /// Get the main game view. + public static EditorWindow GetMainGameView() { + if(Types.GameView.HasMethod("GetMainGameView")) { // Removed in 2019.3 alpha + return Types.GameView.InvokeMethod("GetMainGameView"); + } else if(Types.PreviewEditorWindow.HasMethod("GetMainPreviewWindow")) { // Removed in 2019.3 beta + return Types.PreviewEditorWindow.InvokeMethod("GetMainPreviewWindow"); + } else { // if (Types.PlayModeView.HasMethod("GetMainPlayModeView")) + return Types.PlayModeView.InvokeMethod("GetMainPlayModeView"); + } + } + + /// Get all the game views. This returns even the docked game views which are not visible. + public static EditorWindow[] GetGameViews() { + return Resources + .FindObjectsOfTypeAll(Types.GameView) + .Cast() + .ToArray(); + } + + /// Returns the focused view if it is a dock area with more than one tab, otherwise, returns the focused window. + public static ScriptableObject GetFocusedViewOrWindow() { + var mostSpecificView = Types.GUIView.GetPropertyValue("focusedView"); + + if(!mostSpecificView) + return null; + + // The most specific obj is a window, open it instead of the view + if(mostSpecificView.IsOfType(Types.HostView, false)) + return EditorWindow.focusedWindow; + + var viewHierarchy = GetViewHierarchy(mostSpecificView); + var leastSpecificView = viewHierarchy.LastOrDefault(); + + // The view hierarchy has the same length of all the views in this ContainerWindow + // This means there are no cousins views handled by a split group on the surroundings of this one + // So, we're alone on the container + var alone = leastSpecificView.GetPropertyValue("allChildren").Length == viewHierarchy.Length; + + if(alone && EditorWindow.focusedWindow.InvokeMethod("GetNumTabs") > 1) + alone = false; // But, we may not be the only tab on the host view + + // If the focused view is in the main view, or we are the only child on this view, then we open the window + return alone || leastSpecificView.IsOfType(Types.MainView) ? + EditorWindow.focusedWindow : + leastSpecificView; + } + + /// Returns all the parents of a given view. + public static ScriptableObject[] GetViewHierarchy(ScriptableObject view) { + if(!view) + return new ScriptableObject[0]; + + view.EnsureOfType(Types.View); + + var list = new List() { view }; + var parent = view.GetPropertyValue("parent"); + + while(parent) { // Get the least specific view + view = parent; + list.Add(view); + parent = view.GetPropertyValue("parent"); + } + + return list.ToArray(); + } + + /// Get all the children view of a given view. + public static ScriptableObject[] GetAllViewChildren(ScriptableObject view) { + if(!view) + return new ScriptableObject[0]; + + view.EnsureOfType(Types.View); + + return view.GetPropertyValue("allChildren") + .Cast() + .ToArray(); + } + + /// Returns wheter a given view is focused or not. + public static bool IsViewFocused(ScriptableObject view) { + if(!view) + return false; + + view.EnsureOfType(Types.View); + + var focused = Types.GUIView.GetPropertyValue("focusedView"); + var children = GetAllViewChildren(view); + + return children.Contains(focused); + } + + /// Focus a view. + public static void FocusView(ScriptableObject guiView) { + if(!guiView) + return; + + // guiView.EnsureOfType(Types.GUIView); + if(guiView.IsOfType(Types.GUIView)) + guiView.InvokeMethod("Focus"); + else { + var vp = new ViewPyramid(guiView); + var vc = vp.Container; + var methodName = "Internal_BringLiveAfterCreation"; + + if(vc) { + if(vc.HasMethod(methodName, new Type[] { typeof(bool), typeof(bool), typeof(bool) })) + // displayImmediately, setFocus, showMaximized + vc.InvokeMethod(methodName, false, true, false); + else + // displayImmediately, setFocus + vc.InvokeMethod(methodName, false, true); + } + } + } + + /// Returns the display scaling of the editor, e.g. 1.5 if 125% + public static float GetDisplayScaling() { + return EditorGUIUtility.pixelsPerPoint; + } + + /// Returns a screen rect corrected to fit the editor scaling + public static Rect DpiCorrectedArea(Rect area) { + var scaling = GetDisplayScaling(); + area.width /= scaling; + area.height /= scaling; + return area; + } + + /// Get the default height of the editor toolbars. + public static float GetToolbarHeight() { + try { + if(typeof(EditorGUI).HasField("kWindowToolbarHeight")) { + var result = typeof(EditorGUI).GetFieldValue("kWindowToolbarHeight"); + if(result is int) + return (int)result; + else + return result.GetPropertyValue("value"); + } else + return 17f; // Default on < 2019.3 versions + } catch(Exception e) { + if(FullscreenUtility.DebugModeEnabled) + Debug.LogException(e); + return 17f; + } + } + + /// Set the default height of the editor toolbars. + public static bool SetToolbarHeight(float value) { + try { + // On Unity blow 2019.3 this is a const field and cannot be changed + if(typeof(EditorGUI).HasField("kWindowToolbarHeight")) { + var result = typeof(EditorGUI).GetFieldValue("kWindowToolbarHeight"); + result.SetFieldValue("m_Value", value); + return true; + } else { + return false; + } + } catch(Exception e) { + if(FullscreenUtility.DebugModeEnabled) + Debug.LogException(e); + return false; + } + } + + /// Set Game View target display. + public static void SetGameViewDisplayTarget(EditorWindow gameView, int display) { + gameView.EnsureOfType(Types.GameView); + + if(gameView.HasProperty("targetDisplay")) { + gameView.SetPropertyValue("targetDisplay", display); + } else if(gameView.HasField("m_TargetDisplay")) { + gameView.SetFieldValue("m_TargetDisplay", display); + } else { + Logger.Error("Could not set Game View target display"); + } + } + + } +} diff --git a/Assets/Fullscreen/Editor/FullscreenUtility.cs.meta b/Assets/Fullscreen/Editor/FullscreenUtility.cs.meta new file mode 100644 index 0000000..1e08a3d --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenUtility.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 7b3d579b1fea3fe48a089150e74369a0 +timeCreated: 1508966650 +licenseType: Store +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/FullscreenView.cs b/Assets/Fullscreen/Editor/FullscreenView.cs new file mode 100644 index 0000000..eda1dde --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenView.cs @@ -0,0 +1,63 @@ +using System; +using UnityEditor; +using UnityEngine; +using ContainerWindow = UnityEngine.ScriptableObject; +using HostView = UnityEngine.ScriptableObject; +using View = UnityEngine.ScriptableObject; + +namespace FullscreenEditor { + public class FullscreenView : FullscreenContainer { + + protected void SwapViews(View a, View b) { + var containerA = a.GetPropertyValue("window"); + var containerB = b.GetPropertyValue("window"); + + SetFreezeContainer(containerA, true); + SetFreezeContainer(containerB, true); + + Logger.Debug("Swapping views {0} and {1} @ {2} and {3}", a, b, containerA, containerB); + + containerA.SetPropertyValue("rootView", b); + containerB.SetPropertyValue("rootView", a); + + SetFreezeContainer(containerA, true); + SetFreezeContainer(containerB, true); + } + + internal void OpenView(Rect rect, ScriptableObject view) { + if(!view) + throw new ArgumentNullException("view"); + + view.EnsureOfType(Types.View); + + if(FullscreenUtility.IsLinux) + throw new PlatformNotSupportedException("Linux does not support fullscreen from View class"); + + if(Fullscreen.GetFullscreenFromView(view)) { + Logger.Debug("Tried to fullscreen a view already in fullscreen"); + return; + } + + BeforeOpening(); + + var placeholder = CreateInstance(); + + m_src = new ViewPyramid(view); + m_dst = CreateFullscreenViewPyramid(rect, placeholder); + + SwapViews(m_src.View, m_dst.View); + Rect = rect; + + AfterOpening(); + } + + public override void Close() { + + if(m_src.View && m_dst.View) + SwapViews(m_src.View, m_dst.View); // Swap back the source view + + base.Close(); + } + + } +} diff --git a/Assets/Fullscreen/Editor/FullscreenView.cs.meta b/Assets/Fullscreen/Editor/FullscreenView.cs.meta new file mode 100644 index 0000000..391b19f --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 050fcc381b31f914db19a2e191357a66 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/FullscreenWindow.cs b/Assets/Fullscreen/Editor/FullscreenWindow.cs new file mode 100644 index 0000000..eba11cf --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenWindow.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using ContainerWindow = UnityEngine.ScriptableObject; +using HostView = UnityEngine.ScriptableObject; +using View = UnityEngine.ScriptableObject; + +namespace FullscreenEditor { + public class FullscreenWindow : FullscreenContainer { + + [SerializeField] private RectOffset m_rectOffset; + [SerializeField] private RectOffset m_toolbarOffset; + [SerializeField] private bool m_createdByFullscreenOnPlay; + + public RectOffset ClipOffset { + get { return m_rectOffset; } + set { + if(m_dst.View) { + m_rectOffset = value; + m_dst.View.InvokeMethod("SetPosition", value.Add(new Rect(Vector2.zero, Rect.size))); + } + } + } + + internal bool CreatedByFullscreenOnPlay { + get { return m_createdByFullscreenOnPlay; } + set { m_createdByFullscreenOnPlay = value; } + } + + public bool HasToolbarOffset { get { return ToolbarOffset != null; } } + + public virtual RectOffset ToolbarOffset { + get { + if(m_toolbarOffset == null) + m_toolbarOffset = new RectOffset(0, 0, (int)FullscreenUtility.GetToolbarHeight(), 0); + return m_toolbarOffset; + } + } + + private void SwapWindows(EditorWindow a, EditorWindow b) { + var parentA = a.GetFieldValue("m_Parent"); + var parentB = b.GetFieldValue("m_Parent"); + + var containerA = parentA.GetPropertyValue("window"); + var containerB = parentB.GetPropertyValue("window"); + + var selectedPaneA = parentA.GetPropertyValue("actualView"); + var selectedPaneB = parentB.GetPropertyValue("actualView"); + + SetFreezeContainer(containerA, true); + SetFreezeContainer(containerB, true); + + Logger.Debug("Swapping windows {0} and {1} @ {2} and {3}", a, b, parentA, parentB); + + parentA.SetPropertyValue("actualView", b); + parentB.SetPropertyValue("actualView", a); + + ReplaceDockAreaPane(parentA, a, b); + ReplaceDockAreaPane(parentB, b, a); + + a.InvokeMethod("MakeParentsSettingsMatchMe"); + b.InvokeMethod("MakeParentsSettingsMatchMe"); + + if(selectedPaneA != a) + parentA.SetPropertyValue("actualView", selectedPaneA); + if(selectedPaneB != b) + parentB.SetPropertyValue("actualView", selectedPaneB); + + SetFreezeContainer(containerA, false); + SetFreezeContainer(containerB, false); + } + + protected void ReplaceDockAreaPane(View dockArea, EditorWindow originalPane, EditorWindow newPane) { + if(dockArea.HasField("m_Panes")) { + var dockedPanes = dockArea.GetFieldValue>("m_Panes"); + var dockIndex = dockedPanes.IndexOf(originalPane); + dockedPanes[dockIndex] = newPane; + } + } + + public void SetToolbarStatus(bool toolbarVisible) { + if(!HasToolbarOffset) + return; + + if(FullscreenPreferences.UseGlobalToolbarHiding) + return; + + ClipOffset = toolbarVisible ? new RectOffset() : ToolbarOffset; + } + + public override void Focus() { + var window = ActualViewPyramid.Window; + + if(window) + window.Focus(); + else + base.Focus(); + } + + public override bool IsFocused() { + return EditorWindow.focusedWindow && EditorWindow.focusedWindow == ActualViewPyramid.Window; + } + + protected override void AfterOpening() { + base.AfterOpening(); + + Focus(); + + if(m_src.Window) + m_dst.Window.titleContent = m_src.Window.titleContent; // Copy the title of the window to the placeholder + + SetToolbarStatus(FullscreenPreferences.ToolbarVisible); // Hide/show the toolbar + // macOS doesn't like fast things, so we'll wait a bit and do it again + // Looks like Linux does not like it too + After.Milliseconds(100d, () => SetToolbarStatus(FullscreenPreferences.ToolbarVisible)); + + var notificationWindow = ActualViewPyramid.Window; + + After.Milliseconds(50d, () => { + if(!notificationWindow) // Might have been closed + return; + + var menuItemPath = string.Empty; + if(notificationWindow.IsOfType(Types.GameView)) { + menuItemPath = Fullscreen + .GetAllFullscreen() + .Where(fs => fs.ActualViewPyramid.Window && fs.ActualViewPyramid.Window.IsOfType(Types.GameView)) + .Count() > 1 ? + Shortcut.MOSAIC_PATH : + Shortcut.GAME_VIEW_PATH; + } else if(notificationWindow is SceneView) + menuItemPath = Shortcut.SCENE_VIEW_PATH; + else + menuItemPath = Shortcut.CURRENT_VIEW_PATH; + + FullscreenUtility.ShowFullscreenExitNotification(notificationWindow, menuItemPath); + }); + + } + + protected override void OnEnable() { + base.OnEnable(); + FullscreenPreferences.ToolbarVisible.OnValueSaved += SetToolbarStatus; + } + + protected override void OnDisable() { + base.OnDisable(); + FullscreenPreferences.ToolbarVisible.OnValueSaved -= SetToolbarStatus; + } + + internal void OpenWindow(Rect rect, T window = null) where T : EditorWindow { + OpenWindow(rect, typeof(T), window); + } + + internal void OpenWindow(Rect rect, Type type, EditorWindow window = null, bool disposableWindow = false) { + if(type == null) + throw new ArgumentNullException("type"); + + if(!type.IsOfType(typeof(EditorWindow))) + throw new ArgumentException("Type must be inherited from UnityEditor.EditorWindow", "type"); + + if(window is PlaceholderWindow) { + FullscreenUtility.ShowFullscreenNotification(window, "Wanna fullscreen the placeholder?\nSorry, not possible"); + Logger.Debug("Tried to fullscreen a placeholder window"); + return; + } + + if(Fullscreen.GetFullscreenFromView(window)) { + FullscreenUtility.ShowFullscreenNotification(window, "You can't fullscreen a window already in fullscreen"); + Logger.Debug("Tried to fullscreen a view already in fullscreen"); + return; + } + + if(window && window.HasProperty("isFullscreen") && window.GetPropertyValue("isFullscreen")) { + Logger.Debug("Tried to fullscreen a view already using Unity's built-in fullscreen"); + window.ShowNotification(new GUIContent("This is a built-in fullscreen and not a Fullscreen Editor instance. Use Ctrl+Shift+F7 or Alt-F4 to close it.")); + return; + } + + BeforeOpening(); + + if(window) + m_src = new ViewPyramid(window); + + var childWindow = window ? + (EditorWindow)CreateInstance() : + (EditorWindow)CreateInstance(type); // Instantiate a new window for this fullscreen + + m_dst = CreateFullscreenViewPyramid(rect, childWindow); + + if(window) // We can't swap the src window if we didn't create a placeholder window + SwapWindows(m_src.Window, m_dst.Window); + + Rect = rect; + + if(disposableWindow && childWindow is PlaceholderWindow) { + childWindow.Close(); // Close the pyramid we created because disposable views are not restore later + m_dst.Window = m_src.Window; + } + + AfterOpening(); + } + + internal bool IsPlaceholderVisible() { + if(!(m_dst.Window is PlaceholderWindow)) + return false; + + var pyramid = new ViewPyramid(m_dst.Window); + + if(!pyramid.View || !pyramid.View.IsOfType(Types.HostView)) + return false; + + var actualView = pyramid.View.GetPropertyValue("actualView"); + + return actualView == m_dst.Window; + } + + public override void Close() { + + var shouldRefocus = IsFocused() && IsPlaceholderVisible(); + + if(m_src.Window && m_dst.Window) + SwapWindows(m_src.Window, m_dst.Window); // Swap back the source window + + base.Close(); + + if(shouldRefocus && m_src.Window) + m_src.Window.Focus(); + } + + } +} diff --git a/Assets/Fullscreen/Editor/FullscreenWindow.cs.meta b/Assets/Fullscreen/Editor/FullscreenWindow.cs.meta new file mode 100644 index 0000000..ae31789 --- /dev/null +++ b/Assets/Fullscreen/Editor/FullscreenWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b311009b9693cf4f89b79d7ce034d70 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/GameViewLowResolutionAspectRatios.cs b/Assets/Fullscreen/Editor/GameViewLowResolutionAspectRatios.cs new file mode 100644 index 0000000..2c685c1 --- /dev/null +++ b/Assets/Fullscreen/Editor/GameViewLowResolutionAspectRatios.cs @@ -0,0 +1,18 @@ +using UnityEditor; + +namespace FullscreenEditor { + [InitializeOnLoad] + // Issues #98 #96 #97 and #99 + public class GameViewLowResolutionAspectRatios { + + static GameViewLowResolutionAspectRatios() { + FullscreenCallbacks.afterFullscreenOpen += fs => { + var window = fs.ActualViewPyramid.Window; + + if (window && window.HasProperty("lowResolutionForAspectRatios")) + window.SetPropertyValue("lowResolutionForAspectRatios", false); + }; + } + + } +} diff --git a/Assets/Fullscreen/Editor/GameViewLowResolutionAspectRatios.cs.meta b/Assets/Fullscreen/Editor/GameViewLowResolutionAspectRatios.cs.meta new file mode 100644 index 0000000..43352c6 --- /dev/null +++ b/Assets/Fullscreen/Editor/GameViewLowResolutionAspectRatios.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5a92ff5ca1709dd43bd7675f90970e44 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/GameViewVSync.cs b/Assets/Fullscreen/Editor/GameViewVSync.cs new file mode 100644 index 0000000..837f140 --- /dev/null +++ b/Assets/Fullscreen/Editor/GameViewVSync.cs @@ -0,0 +1,35 @@ +using UnityEditor; + +namespace FullscreenEditor { + [InitializeOnLoad] + public class GameViewVSync { + + static GameViewVSync() { + FullscreenCallbacks.afterFullscreenOpen += (fs) => { + RefreshViewVSync(fs.ActualViewPyramid.Window); + }; + + FullscreenCallbacks.afterFullscreenClose += (fs) => { + RefreshViewVSync(fs.m_src.Window); + }; + } + + private static void RefreshViewVSync(EditorWindow window) { + if (window && window.HasProperty("vSyncEnabled")) { + var vsyncEnabled = window.GetPropertyValue("vSyncEnabled"); + + // reset vsync + window.SetPropertyValue("vSyncEnabled", vsyncEnabled); + + var view = new ViewPyramid(window).View; + + // fallback when above doesn't work + if (view.HasMethod("EnableVSync")) + view.InvokeMethod("EnableVSync", vsyncEnabled); + else + Logger.Debug(string.Format("View {0} does not support vsync", view.GetType())); + } + } + + } +} diff --git a/Assets/Fullscreen/Editor/GameViewVSync.cs.meta b/Assets/Fullscreen/Editor/GameViewVSync.cs.meta new file mode 100644 index 0000000..74ab5f4 --- /dev/null +++ b/Assets/Fullscreen/Editor/GameViewVSync.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dee0189166f305e43b19aa0dea5568c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/GlobalToolbarHiding.cs b/Assets/Fullscreen/Editor/GlobalToolbarHiding.cs new file mode 100644 index 0000000..39823a2 --- /dev/null +++ b/Assets/Fullscreen/Editor/GlobalToolbarHiding.cs @@ -0,0 +1,41 @@ +using UnityEditor; + +namespace FullscreenEditor { + [InitializeOnLoad] + public class GlobalToolbarHiding { + + private static readonly float defaultToolbarHeight; + + private static bool GlobalToolbarShouldBeHidden { + get { + return !FullscreenPreferences.ToolbarVisible && + Fullscreen.GetAllFullscreen(false).Length > 0; + } + } + + static GlobalToolbarHiding() { + defaultToolbarHeight = FullscreenUtility.GetToolbarHeight(); + + FullscreenPreferences.UseGlobalToolbarHiding.OnValueSaved += v => { + if (!v) + FullscreenUtility.SetToolbarHeight(defaultToolbarHeight); + }; + + FullscreenPreferences.ToolbarVisible.OnValueSaved += v => UpdateGlobalToolbarStatus(); + UpdateGlobalToolbarStatus(); + + After.Frames(2, () => // Why? IDK + UpdateGlobalToolbarStatus() + ); + + FullscreenCallbacks.afterFullscreenClose += fs => UpdateGlobalToolbarStatus(); + FullscreenCallbacks.afterFullscreenOpen += fs => UpdateGlobalToolbarStatus(); + } + + public static void UpdateGlobalToolbarStatus() { + if (FullscreenPreferences.UseGlobalToolbarHiding) + FullscreenUtility.SetToolbarHeight(GlobalToolbarShouldBeHidden ? 0f : defaultToolbarHeight); + } + + } +} diff --git a/Assets/Fullscreen/Editor/GlobalToolbarHiding.cs.meta b/Assets/Fullscreen/Editor/GlobalToolbarHiding.cs.meta new file mode 100644 index 0000000..4d4a7d8 --- /dev/null +++ b/Assets/Fullscreen/Editor/GlobalToolbarHiding.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2d6d3d596f5ac6a45a6a4b9fc0f431c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Integration.cs b/Assets/Fullscreen/Editor/Integration.cs new file mode 100644 index 0000000..8e45f74 --- /dev/null +++ b/Assets/Fullscreen/Editor/Integration.cs @@ -0,0 +1,81 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace FullscreenEditor { + /// Helper class for enabling/disabling compilation symbols. + public static class Integration { + + private static string[] GetAllDefines() { + var currentBuildTarget = EditorUserBuildSettings.selectedBuildTargetGroup; + var scriptDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(currentBuildTarget); + var split = scriptDefines.Split(new [] { ';', ' ' }, StringSplitOptions.RemoveEmptyEntries); + + return split; + } + + private static void SetAllDefines(string[] value) { + var currentBuildTarget = EditorUserBuildSettings.selectedBuildTargetGroup; + var currentScriptDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(currentBuildTarget); + var scriptDefines = value.Length > 0 ? + value.Aggregate((a, b) => a + ";" + b) : + string.Empty; + + if (currentScriptDefines == scriptDefines) + return; // Nothing has changed + + PlayerSettings.SetScriptingDefineSymbolsForGroup(currentBuildTarget, scriptDefines); + + RequestScriptReload(); + } + + public static void RequestScriptReload() { + if (typeof(EditorUtility).HasMethod("RequestScriptReload")) { + typeof(EditorUtility).InvokeMethod("RequestScriptReload"); + } + if (typeof(InternalEditorUtility).HasMethod("RequestScriptReload")) { + typeof(InternalEditorUtility).InvokeMethod("RequestScriptReload"); + } else { + Logger.Error("Could not reload scripts"); + } + + } + + /// Toggle a given define symbol. + /// The define symbol to toggle. + public static void ToggleDirectiveDefined(string directive) { + var defined = IsDirectiveDefined(directive); + SetDirectiveDefined(directive, !defined); + } + + /// Enable or disable a given define symbol. + /// The define symbol to set. + /// Wheter to enable or disable this directive. + public static void SetDirectiveDefined(string directive, bool enabled) { + if (IsDirectiveDefined(directive) == enabled) + return; // Flag already enabled/disabled + + if (enabled) + SetAllDefines(GetAllDefines() + .Concat(new [] { directive }) + .ToArray() + ); + else + SetAllDefines(GetAllDefines() + .Where(d => d != directive) + .ToArray() + ); + + Logger.Debug("Compiler directive {0} {1} defined", directive, enabled? "": "not"); + } + + /// Get wheter the given directive is enabled or not. + /// The name of the define symbol to check. + public static bool IsDirectiveDefined(string directive) { + return GetAllDefines().Any(d => d == directive); + } + + } +} diff --git a/Assets/Fullscreen/Editor/Integration.cs.meta b/Assets/Fullscreen/Editor/Integration.cs.meta new file mode 100644 index 0000000..6c4b7c4 --- /dev/null +++ b/Assets/Fullscreen/Editor/Integration.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3d15b472fc220e4aa5fa2cb88e3b16c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/InternalTypes.cs b/Assets/Fullscreen/Editor/InternalTypes.cs new file mode 100644 index 0000000..f194d9c --- /dev/null +++ b/Assets/Fullscreen/Editor/InternalTypes.cs @@ -0,0 +1,42 @@ +using System; + +namespace FullscreenEditor { + /// Class containing types of UnityEditor internal classes. + public static class Types { + + /// UnityEditor.HostView + public static readonly Type HostView = ReflectionUtility.FindClass("UnityEditor.HostView"); + + /// UnityEditor.ContainerWindow + public static readonly Type ContainerWindow = ReflectionUtility.FindClass("UnityEditor.ContainerWindow"); + + /// UnityEditor.View + public static readonly Type View = ReflectionUtility.FindClass("UnityEditor.View"); + + /// UnityEditor.GUIView + public static readonly Type GUIView = ReflectionUtility.FindClass("UnityEditor.GUIView"); + + /// UnityEditor.GameView + public static readonly Type GameView = ReflectionUtility.FindClass("UnityEditor.GameView"); + + /// UnityEditor.PreviewEditorWindow + public static readonly Type PreviewEditorWindow = ReflectionUtility.FindClass("UnityEditor.PreviewEditorWindow"); + + /// UnityEditor.PlayModeView + public static readonly Type PlayModeView = ReflectionUtility.FindClass("UnityEditor.PlayModeView"); + + /// UnityEditor.MainView + public static readonly Type MainView = ReflectionUtility.FindClass("UnityEditor.MainView"); + + /// UnityEditor.WindowLayout + public static readonly Type WindowLayout = ReflectionUtility.FindClass("UnityEditor.WindowLayout"); + + /// UnityEngine.EnumDataUtility + public static readonly Type EnumDataUtility = ReflectionUtility.FindClass("UnityEngine.EnumDataUtility"); + + /// UnityEditor.PlayModeView.EnterPlayModeBehavior + // Enum type + public static readonly Type EnterPlayModeBehavior = PlayModeView?.GetNestedType("EnterPlayModeBehavior"); + + } +} diff --git a/Assets/Fullscreen/Editor/InternalTypes.cs.meta b/Assets/Fullscreen/Editor/InternalTypes.cs.meta new file mode 100644 index 0000000..498b59d --- /dev/null +++ b/Assets/Fullscreen/Editor/InternalTypes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: af47dde264c784a49afe8110a07f82c8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/KeepFullscreenBelow.cs b/Assets/Fullscreen/Editor/KeepFullscreenBelow.cs new file mode 100644 index 0000000..671fd4b --- /dev/null +++ b/Assets/Fullscreen/Editor/KeepFullscreenBelow.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace FullscreenEditor { + internal static class KeepFullscreenBelow { + [InitializeOnLoadMethod] + private static void InitPatch() { + var eApp = typeof(EditorApplication); + var callback = eApp.GetFieldValue("windowsReordered"); + callback += () => BringWindowsAbove(); + eApp.SetFieldValue("windowsReordered", callback); + FullscreenCallbacks.afterFullscreenOpen += (f) => BringWindowsAbove(); + } + + // https://github.com/mukaschultze/fullscreen-editor/issues/54 + // This is needed because ContainerWindows created by ShowAsDropDown are not + // returned by 'windows' property + public static IEnumerable GetAllContainerWindowsOrdered() { + var ordered = Types.ContainerWindow + .GetPropertyValue("windows") + .Reverse(); + + var missing = Resources + .FindObjectsOfTypeAll(Types.ContainerWindow) + .Select(cw => cw as ScriptableObject); + + return ordered + .Concat(missing) + .Distinct(); + } + + public static void BringWindowsAbove() { + + if (!FullscreenPreferences.KeepFullscreenBelow) + return; + + var fullscreens = Fullscreen.GetAllFullscreen(); + if (fullscreens.Length == 0) + return; + + var methodName = "Internal_BringLiveAfterCreation"; + var windows = GetAllContainerWindowsOrdered() + .Where(w => !Fullscreen.GetFullscreenFromView(w)) + .Where(w => { + if (w.GetPropertyValue("showMode") == (int)ShowMode.MainWindow) + return false; // Main Window should be kept below everything + + if (fullscreens.FirstOrDefault((f) => f.m_src.Container == w)) + return false; // Keep other fullscreen containers below + + return true; + }); + + foreach (var w in windows) { + if (w.HasMethod(methodName, new Type[] { typeof(bool), typeof(bool), typeof(bool) })) + w.InvokeMethod(methodName, true, false, false); + else + w.InvokeMethod(methodName, true, false); + } + } + } +} diff --git a/Assets/Fullscreen/Editor/KeepFullscreenBelow.cs.meta b/Assets/Fullscreen/Editor/KeepFullscreenBelow.cs.meta new file mode 100644 index 0000000..ff62473 --- /dev/null +++ b/Assets/Fullscreen/Editor/KeepFullscreenBelow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d0e2f75c74537fd47ba55a34fa3fd7ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Linux.meta b/Assets/Fullscreen/Editor/Linux.meta new file mode 100644 index 0000000..3e4c33a --- /dev/null +++ b/Assets/Fullscreen/Editor/Linux.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f159b5c7b27f37c1cac894c9b84d8069 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Linux/NativeFullscreenHook.cs b/Assets/Fullscreen/Editor/Linux/NativeFullscreenHook.cs new file mode 100644 index 0000000..137d591 --- /dev/null +++ b/Assets/Fullscreen/Editor/Linux/NativeFullscreenHook.cs @@ -0,0 +1,22 @@ +using UnityEditor; + +namespace FullscreenEditor.Linux { + internal static class NativeFullscreenHooks { + + [InitializeOnLoadMethod] + private static void Init() { + if (!FullscreenUtility.IsLinux) + return; + + FullscreenCallbacks.afterFullscreenOpen += (fs) => { + if (wmctrl.IsInstalled && !FullscreenPreferences.DoNotUseWmctrl.Value) + wmctrl.SetNativeFullscreen(true, fs.m_dst.Container); + }; + FullscreenCallbacks.beforeFullscreenClose += (fs) => { + if (wmctrl.IsInstalled && !FullscreenPreferences.DoNotUseWmctrl.Value) + wmctrl.SetNativeFullscreen(false, fs.m_dst.Container); + }; + } + + } +} diff --git a/Assets/Fullscreen/Editor/Linux/NativeFullscreenHook.cs.meta b/Assets/Fullscreen/Editor/Linux/NativeFullscreenHook.cs.meta new file mode 100644 index 0000000..984106e --- /dev/null +++ b/Assets/Fullscreen/Editor/Linux/NativeFullscreenHook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 848050aee5eb0cd9f904d899474c60d1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Linux/wmctrl.cs b/Assets/Fullscreen/Editor/Linux/wmctrl.cs new file mode 100644 index 0000000..0aceffb --- /dev/null +++ b/Assets/Fullscreen/Editor/Linux/wmctrl.cs @@ -0,0 +1,75 @@ +using System; +using UnityEditor; +using UnityEngine; + +namespace FullscreenEditor.Linux { + /// wmctrl is a tool to interact with an X Window manager available on Linux platforms. + public static class wmctrl { + + static wmctrl() { + try { + var stdout = string.Empty; + var stderr = string.Empty; + var exitCode = Cmd.Run("which wmctrl", false, out stdout, out stderr); + + IsInstalled = exitCode == 0; + } catch (Exception e) { + Logger.Debug("Could not run command 'which wmctrl': {0}", e); + IsInstalled = false; + } + } + + public static readonly bool IsInstalled; + + private static string Run(string format, params object[] args) { + if (!FullscreenUtility.IsLinux) + throw new PlatformNotSupportedException("wmctrl is only available on Linux based platforms"); + + if (FullscreenPreferences.DoNotUseWmctrl.Value) { + Logger.Debug("wmctrl being invoked while DoNotUseWmctrl is enabled"); + } + + var result = Cmd.Run("wmctrl " + format, args); + Logger.Debug("wmctrl exited with stdio: {0}", result); + return result; + } + + /// Enable or disable native fullscreen for a given window. + /// Should the window be fullscreen or not. + /// The window to changed. If null the active window will be fullscreened. + public static void SetNativeFullscreen(bool fullscreen, EditorWindow window) { + if (window) + window.Focus(); + + Run("-r ':ACTIVE:' -b {0},fullscreen", fullscreen ? "add" : "remove"); + } + + /// Enable or disable native fullscreen for a given view. + /// Should the view be fullscreen or not. + /// The view to changed. If null the active view will be fullscreened. + public static void SetNativeFullscreen(bool fullscreen, ScriptableObject view) { + if (view) + FullscreenUtility.FocusView(view); + + Run("-r ':ACTIVE:' -b {0},fullscreen", fullscreen ? "add" : "remove"); + } + + /// Toggles native fullscreen for a given window. + /// The window to be toggled fullscreen. + public static void ToggleNativeFullscreen(EditorWindow window) { + if (window) + window.Focus(); + + Run("-r ':ACTIVE:' -b toggle,fullscreen"); + } + + /// Toggles native fullscreen for a given view. + /// The view to be toggled fullscreen. + public static void ToggleNativeFullscreen(ScriptableObject view) { + if (view) + FullscreenUtility.FocusView(view); + + Run("-r ':ACTIVE:' -b toggle,fullscreen"); + } + } +} diff --git a/Assets/Fullscreen/Editor/Linux/wmctrl.cs.meta b/Assets/Fullscreen/Editor/Linux/wmctrl.cs.meta new file mode 100644 index 0000000..f634e89 --- /dev/null +++ b/Assets/Fullscreen/Editor/Linux/wmctrl.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8385b56377b3215b59deee56f21c295b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Logger.cs b/Assets/Fullscreen/Editor/Logger.cs new file mode 100644 index 0000000..805fc59 --- /dev/null +++ b/Assets/Fullscreen/Editor/Logger.cs @@ -0,0 +1,56 @@ +using System; +using System.Diagnostics; +using UnityEngine; +using Object = UnityEngine.Object; +using UnityDebug = UnityEngine.Debug; + +namespace FullscreenEditor { + /// Helper class for logging and debugging. + public static class Logger { + + private const string LOG_PREFIX = "Fullscreen Editor: "; + + [Conditional("FULLSCREEN_DEBUG")] + public static void Debug(string message, params object[] args) { + UnityDebug.LogFormat(LOG_PREFIX + message, args); + } + + [Conditional("FULLSCREEN_DEBUG")] + public static void Debug(Object context, string message, params object[] args) { + UnityDebug.LogFormat(context, LOG_PREFIX + message, args); + } + + public static void Log(string message, params object[] args) { + UnityDebug.LogFormat(LOG_PREFIX + message, args); + } + + public static void Log(Object context, string message, params object[] args) { + UnityDebug.LogFormat(context, LOG_PREFIX + message, args); + } + + public static void Warning(string message, params object[] args) { + UnityDebug.LogWarningFormat(LOG_PREFIX + message, args); + } + + public static void Warning(Object context, string message, params object[] args) { + UnityDebug.LogWarningFormat(context, LOG_PREFIX + message, args); + } + + public static void Error(string message, params object[] args) { + UnityDebug.LogErrorFormat(LOG_PREFIX + message, args); + } + + public static void Error(Object context, string message, params object[] args) { + UnityDebug.LogErrorFormat(context, LOG_PREFIX + message, args); + } + + public static void Exception(Exception exception) { + UnityDebug.LogException(exception); + } + + public static void Exception(Object context, Exception exception) { + UnityDebug.LogException(exception, context); + } + + } +} diff --git a/Assets/Fullscreen/Editor/Logger.cs.meta b/Assets/Fullscreen/Editor/Logger.cs.meta new file mode 100644 index 0000000..5db6b40 --- /dev/null +++ b/Assets/Fullscreen/Editor/Logger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 10b1d1ff89b6766448444cad56571715 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/MenuItems.cs b/Assets/Fullscreen/Editor/MenuItems.cs new file mode 100644 index 0000000..f96268f --- /dev/null +++ b/Assets/Fullscreen/Editor/MenuItems.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections; +using System.Linq; +using FullscreenEditor.Linux; +using FullscreenEditor.Windows; +using UnityEditor; +using UnityEngine; + +namespace FullscreenEditor { + internal static class MenuItems { + + [MenuItem(Shortcut.TOOLBAR_PATH, true)] + [MenuItem(Shortcut.FULLSCREEN_ON_PLAY_PATH, true)] + private static bool SetCheckMarks() { + Menu.SetChecked(Shortcut.TOOLBAR_PATH, FullscreenPreferences.ToolbarVisible); + Menu.SetChecked(Shortcut.FULLSCREEN_ON_PLAY_PATH, FullscreenPreferences.FullscreenOnPlayEnabled); + return true; + } + + [MenuItem(Shortcut.TOOLBAR_PATH, false, 0)] + private static void Toolbar() { + FullscreenPreferences.ToolbarVisible.Value = !FullscreenPreferences.ToolbarVisible; + } + + [MenuItem(Shortcut.FULLSCREEN_ON_PLAY_PATH, false, 0)] + private static void FullscreenOnPlay() { + FullscreenPreferences.FullscreenOnPlayEnabled.Value = !FullscreenPreferences.FullscreenOnPlayEnabled; + } + + [MenuItem(Shortcut.CURRENT_VIEW_PATH, false, 100)] + private static void CVMenuItem() { + var focusedView = FullscreenUtility.IsLinux ? + EditorWindow.focusedWindow : // Linux does not support View fullscreen, only EditorWindow + FullscreenUtility.GetFocusedViewOrWindow(); + + if (!focusedView || focusedView is PlaceholderWindow) + return; + + if (focusedView is EditorWindow) + Fullscreen.ToggleFullscreen(focusedView as EditorWindow); + else + Fullscreen.ToggleFullscreen(focusedView); + } + + [MenuItem(Shortcut.GAME_VIEW_PATH, false, 100)] + private static void GVMenuItem() { + var gameView = FindCandidateForFullscreen(Types.PlayModeView ?? Types.GameView, FullscreenUtility.GetMainGameView()); + Fullscreen.ToggleFullscreen(Types.GameView, gameView); + } + + [MenuItem(Shortcut.SCENE_VIEW_PATH, false, 100)] + private static void SVMenuItem() { + var sceneView = FindCandidateForFullscreen(SceneView.lastActiveSceneView); + Fullscreen.ToggleFullscreen(sceneView); + } + + [MenuItem(Shortcut.MAIN_VIEW_PATH, false, 100)] + private static void MVMenuItem() { + var mainView = FullscreenUtility.GetMainView(); + + if (FullscreenUtility.IsLinux) { + if (wmctrl.IsInstalled) + wmctrl.ToggleNativeFullscreen(mainView); + else + Logger.Warning("wmctrl not installed, cannot fullscreen main view. Install it using 'sudo apt-get install wmctrl'"); + return; + } + + if (!mainView) { + Logger.Error("No Main View found, this should not happen"); + return; + } + + Fullscreen.ToggleFullscreen(mainView); + } + + [MenuItem(Shortcut.MOSAIC_PATH, true, 100)] + private static bool MosaicValidate() { + return FullscreenRects.ScreenCount >= 2; + } + + [MenuItem(Shortcut.MOSAIC_PATH, false, 100)] + private static void MosaicMenuItem() { + + var openFullscreens = Fullscreen.GetAllFullscreen(); + + if (openFullscreens.Length > 0) { + foreach (var fs in openFullscreens) + fs.Close(); + return; + } + + var displays = DisplayInfo + .GetDisplays() + .Where(d => (d.displayDevice.StateFlags & DisplayDeviceStateFlags.AttachedToDesktop) != 0) + .ToList(); + + for (var i = 0; i < displays.Count && i < 8; i++) { + var targetDisplay = FullscreenPreferences.MosaicMapping.Value[i]; + + if (targetDisplay < 0) { + continue; // -1 means none + } + + var candidate = FindCandidateForFullscreen(Types.GameView, FullscreenUtility.GetMainGameView()); + + if (candidate) { + candidate = EditorWindow.Instantiate(candidate); + candidate.Show(); + } + + var fs = ScriptableObject.CreateInstance(); + var rect = displays[i].UnityCorrectedArea; + fs.OpenWindow(rect, Types.GameView, candidate, true); + + var gameView = fs.ActualViewPyramid.Window; + + FullscreenUtility.SetGameViewDisplayTarget(gameView, targetDisplay); + + } + } + + [MenuItem(Shortcut.CLOSE_ALL_FULLSCREEN, false, 250)] + private static void CloseAll() { + foreach (var fs in Fullscreen.GetAllFullscreen()) + fs.Close(); + } + + [MenuItem(Shortcut.CLOSE_ALL_FULLSCREEN, true, 250)] + private static bool CloseAllValidate() { + return Fullscreen.GetAllFullscreen().Length > 0; + } + + [MenuItem(Shortcut.PREFERENCES_PATH, false, 1000)] + private static void OpenPreferences() { +#if UNITY_2018_3_OR_NEWER + var windowType = ReflectionUtility.FindClass("UnityEditor.SettingsWindow"); + windowType.InvokeMethod("Show", SettingsScope.User, "Preferences/Fullscreen Editor"); +#else + var windowType = ReflectionUtility.FindClass("UnityEditor.PreferencesWindow"); + windowType.InvokeMethod("ShowPreferencesWindow"); + After.Frames(3, () => { + var window = EditorWindow.GetWindow(windowType); + var sections = window.GetFieldValue("m_Sections").Cast().ToList(); + var index = sections.FindIndex(section => section.GetFieldValue("content").text == "Fullscreen"); + window.SetPropertyValue("selectedSectionIndex", index); + }); +#endif + } + + private static T FindCandidateForFullscreen(T mainCandidate = null) where T : EditorWindow { + return FindCandidateForFullscreen(typeof(T), mainCandidate) as T; + } + + private static EditorWindow FindCandidateForFullscreen(Type type, EditorWindow mainCandidate = null) { + if (type == null) + throw new ArgumentNullException("type"); + + if (!type.IsOfType(typeof(EditorWindow))) + throw new ArgumentException("Invalid type, type must inherit from UnityEditor.EditorWindow", "type"); + + if (mainCandidate && !mainCandidate.IsOfType(type)) + throw new ArgumentException("Main candidate type must match the type argument or be null", "mainCandidate"); + + // if (mainCandidate && !Fullscreen.GetFullscreenFromView(mainCandidate)) + if (mainCandidate) + return mainCandidate; // Our candidate is not null and is not fullscreened either + + return Resources // Returns the first window of our type that is not in fullscreen + .FindObjectsOfTypeAll(type) + .Cast() + .FirstOrDefault(window => !Fullscreen.GetFullscreenFromView(window)); + } + + } +} diff --git a/Assets/Fullscreen/Editor/MenuItems.cs.meta b/Assets/Fullscreen/Editor/MenuItems.cs.meta new file mode 100644 index 0000000..828a180 --- /dev/null +++ b/Assets/Fullscreen/Editor/MenuItems.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 52ca7a5600c87a844b5f81e0ec14bd7e +timeCreated: 1509037117 +licenseType: Store +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Patcher.cs b/Assets/Fullscreen/Editor/Patcher.cs new file mode 100644 index 0000000..7378590 --- /dev/null +++ b/Assets/Fullscreen/Editor/Patcher.cs @@ -0,0 +1,137 @@ +using System; +using System.Globalization; +using System.Reflection; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace FullscreenEditor { + // error CS0702: A constraint cannot be special class `System.Delegate' + // Unity 2018.3.11f1 - .NET 3.5 equivalent + // public class Patcher : Patcher where T : Delegate { + // public Patcher(T method, T replacement) : base(method.GetMethodInfo(), replacement.GetMethodInfo()) { } + + // public Patcher(MethodBase method, T replacement) : base(method, replacement.GetMethodInfo()) { } + // } + + public unsafe class Patcher { + + private bool swapped; + private MethodBase method; + private MethodInfo replacement; + private byte[] backup = new byte[25]; + + private IntPtr pBody; + private IntPtr pBorrowed; + + public Patcher(MethodBase method, MethodInfo replacement) { + if(!IsSupported()) + throw new PlatformNotSupportedException("Not supported on non x86_x64 processors"); + + this.method = method; + this.replacement = replacement; + } + + public static bool IsSupported() { + if(FullscreenUtility.IsMacOS) return false; + // Does not work on ARM/M1 macs + return CultureInfo.InvariantCulture.CompareInfo.IndexOf(SystemInfo.processorType, "ARM", CompareOptions.IgnoreCase) == -1 && Environment.Is64BitProcess; + } + + public bool IsPatched() { + // var cursor = (byte * )pBody.ToPointer(); + // var isOriginal = backup.All(b => * (cursor++) == b); + + return swapped; + } + + public void Revert() { + if(!swapped) { + throw new Exception("Methods is not patched"); + } + swapped = false; + + unsafe { + var cursor = (byte*)pBody.ToPointer(); + for(var i = 0; i < backup.Length; i++) { + *(cursor++) = backup[i]; + } + } + } + + public void InvokeOriginal(object obj, params object[] parameters) { + try { + if(IsPatched()) + Revert(); + method.Invoke(obj, parameters); + } finally { + if(!IsPatched()) + SwapMethods(); + } + } + + public void SwapMethods() { + if(swapped) { + throw new Exception("Methods already patched"); + } + swapped = true; + + RuntimeHelpers.PrepareMethod(method.MethodHandle); + RuntimeHelpers.PrepareMethod(replacement.MethodHandle); + + pBody = method.MethodHandle.GetFunctionPointer(); + pBorrowed = replacement.MethodHandle.GetFunctionPointer(); + + unsafe { + + var ptr = (byte*)pBody.ToPointer(); + var ptr2 = (byte*)pBorrowed.ToPointer(); + var ptrDiff = ptr2 - ptr - 5; + var relativeJumpAvailable = ptrDiff < (long)0xFFFFFFFF && ptrDiff > (long)-0xFFFFFFFF; + var doNotUseRelativeJump = true; // See issues #69 and #89 + + Logger.Debug("Relative jump is {0}available \\ {1}bit platform", relativeJumpAvailable ? "" : "not ", sizeof(IntPtr) * 8); + + // Backup orignal opcodes so we can revert it later + for(var i = 0; i < backup.Length; i++) { + backup[i] = *(ptr + i); + } + + if(!doNotUseRelativeJump && relativeJumpAvailable) { + // 32-bit relative jump, available on both 32 and 64 bit arch. + // Debug.Trace($"diff is {ptrDiff} doing relative jmp"); + // Debug.Trace("patching on {0:X}, target: {1:X}", (ulong)ptr, (ulong)ptr2); + *ptr = 0xE9; // JMP + *((uint*)(ptr + 1)) = (uint)ptrDiff; + } else { + // Debug.Trace($"diff is {ptrDiff} doing push+ret trampoline"); + // Debug.Trace("patching on {0:X}, target: {1:X}", (ulong)ptr, (ulong)ptr2); + if(sizeof(IntPtr) == 8) { + // For 64bit arch and likely 64bit pointers, do: + // PUSH bits 0 - 32 of addr + // MOV [RSP+4] bits 32 - 64 of addr + // RET + var cursor = ptr; + *(cursor++) = 0x68; // PUSH + *((uint*)cursor) = (uint)ptr2; + cursor += 4; + *(cursor++) = 0xC7; // MOV [RSP+4] + *(cursor++) = 0x44; + *(cursor++) = 0x24; + *(cursor++) = 0x04; + *((uint*)cursor) = (uint)((ulong)ptr2 >> 32); + cursor += 4; + *(cursor++) = 0xC3; // RET + } else { + // For 32bit arch and 32bit pointers, do: PUSH addr, RET. + *ptr = 0x68; + *((uint*)(ptr + 1)) = (uint)ptr2; + *(ptr + 5) = 0xC3; + } + } + + // Logger.Debug("Patched 0x{0:X} to 0x{1:X}.", (ulong)ptr, (ulong)ptr2); + } + } + + } +} diff --git a/Assets/Fullscreen/Editor/Patcher.cs.meta b/Assets/Fullscreen/Editor/Patcher.cs.meta new file mode 100644 index 0000000..ed9ab73 --- /dev/null +++ b/Assets/Fullscreen/Editor/Patcher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fa394636ccd0ab449adc0275212a1707 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/PlaceholderWindow.cs b/Assets/Fullscreen/Editor/PlaceholderWindow.cs new file mode 100644 index 0000000..ea9228d --- /dev/null +++ b/Assets/Fullscreen/Editor/PlaceholderWindow.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace FullscreenEditor { + /// The window that will be shown in the place of the original view when creating a fullscreen container. + public class PlaceholderWindow : EditorWindow { + + private const float PREVIEW_FRAMERATE = 24f; + + private static class Styles { + + public static readonly GUIStyle textStyle = new GUIStyle("BoldLabel"); + public static readonly GUIStyle backgroundShadow = new GUIStyle("InnerShadowBg"); + public static readonly GUIStyle buttonStyle = new GUIStyle("LargeButton"); + + static Styles() { + textStyle.wordWrap = true; + textStyle.alignment = TextAnchor.MiddleCenter; + } + + } + + [SerializeField] private Vector2 m_scroll; + [SerializeField] private FullscreenContainer m_fullscreenContainer; + [SerializeField] private bool m_containerForcefullyClosed; + + private double m_nextUpdate; + private RenderTexture m_previewRT; + + private FullscreenContainer FullscreenContainer { + get { + if (m_containerForcefullyClosed) + return null; + if (!m_fullscreenContainer) + m_fullscreenContainer = FullscreenUtility.GetRef(name); + if (!m_fullscreenContainer) + m_containerForcefullyClosed = true; + + return m_fullscreenContainer; + } + set { m_fullscreenContainer = value; } + } + + private bool PreviewSupported { + get { + return FullscreenContainer && + !FullscreenContainer.Rect.Overlaps(position) && + FullscreenContainer.m_dst.View && + FullscreenContainer.m_src.Window && + FullscreenContainer.m_dst.View.HasMethod("GrabPixels"); + } + } + + private void Update() { + if (EditorApplication.timeSinceStartup < m_nextUpdate) + return; + + m_nextUpdate = EditorApplication.timeSinceStartup + (1f / PREVIEW_FRAMERATE); + RenderTexture.ReleaseTemporary(m_previewRT); + m_previewRT = null; + + if (!PreviewSupported || m_containerForcefullyClosed) + return; + + var view = FullscreenContainer ? FullscreenContainer.m_dst.View : null; + var width = (int)FullscreenContainer.Rect.width; + var height = (int)FullscreenContainer.Rect.height; + + if (!view || width < 10 || height < 10) + return; + + m_previewRT = RenderTexture.GetTemporary(width, height, 0); + view.InvokeMethod("GrabPixels", m_previewRT, new Rect(0f, 0f, width, height)); + Repaint(); + } + + private void OnDisable() { + RenderTexture.ReleaseTemporary(m_previewRT); + } + + private void OnGUI() { + + using(var scrollScope = new EditorGUILayout.ScrollViewScope(m_scroll)) + using(var mainScope = new EditorGUILayout.VerticalScope(Styles.backgroundShadow)) { + + m_scroll = scrollScope.scrollPosition; + + using(new GUIColor(Color.white, 0.1f)) + if (m_previewRT) { + var rtRect = SystemInfo.graphicsUVStartsAtTop ? + Rect.MinMaxRect(0f, 1f, 1f, 0f) : // Direct3D like + Rect.MinMaxRect(0f, 0f, 1f, 1f); // OpenGL like + + GUI.DrawTextureWithTexCoords(mainScope.rect, m_previewRT, rtRect); + } + + using(new GUIColor(Styles.textStyle.normal.textColor * 0.05f)) + GUI.DrawTexture(mainScope.rect, FullscreenUtility.FullscreenIcon, ScaleMode.ScaleAndCrop); + + GUILayout.FlexibleSpace(); + + using(new EditorGUILayout.HorizontalScope()) { + GUILayout.FlexibleSpace(); + + using(new GUIContentColor(Styles.textStyle.normal.textColor)) + GUILayout.Label(FullscreenUtility.FullscreenIcon, Styles.textStyle); + + using(new EditorGUILayout.VerticalScope()) { + if (FullscreenContainer && FullscreenContainer.ActualViewPyramid.Container) { + GUILayout.Label("The view that lives here is in fullscreen mode", Styles.textStyle); + GUILayout.Label("Don't close this placeholder", Styles.textStyle); + } else { + GUILayout.Label("The view that lived here was forcefully closed while in fullscreen, restore is not available", Styles.textStyle); + GUILayout.Label("Consider using the shortcuts to exit fullscreen", Styles.textStyle); + GUILayout.Label("You may close this placeholder", Styles.textStyle); + } + + } + GUILayout.FlexibleSpace(); + } + + using(new EditorGUILayout.HorizontalScope()) { + GUILayout.FlexibleSpace(); + + if (FullscreenContainer && FullscreenContainer.ActualViewPyramid.Container) { + if (GUILayout.Button("Restore View", Styles.buttonStyle)) + FullscreenContainer.Close(); + } else if (GUILayout.Button("Close Placeholder", Styles.buttonStyle)) + Close(); + GUILayout.FlexibleSpace(); + } + + GUILayout.FlexibleSpace(); + + // This GUI may be called after "this" has been destroyed, that causes a NullReference for the "name" getter + // Not sure why this happens tho + EditorGUILayout.LabelField(SafeObjToString(() => name)); + EditorGUILayout.Space(); + + if (FullscreenUtility.DebugModeEnabled) { + EditorGUILayout.LabelField("Forcefully Closed", SafeObjToString(() => m_containerForcefullyClosed)); + + EditorGUILayout.LabelField("Container", SafeObjToString(() => FullscreenContainer)); + EditorGUILayout.LabelField("Rect", SafeObjToString(() => FullscreenContainer.Rect)); + + EditorGUILayout.LabelField("SRC Window", SafeObjToString(() => FullscreenContainer.m_src.Window)); + EditorGUILayout.LabelField("SRC View", SafeObjToString(() => FullscreenContainer.m_src.View)); + EditorGUILayout.LabelField("SRC Container", SafeObjToString(() => FullscreenContainer.m_src.Container)); + + EditorGUILayout.LabelField("DST Window", SafeObjToString(() => FullscreenContainer.m_dst.Window)); + EditorGUILayout.LabelField("DST View", SafeObjToString(() => FullscreenContainer.m_dst.View)); + EditorGUILayout.LabelField("DST Container", SafeObjToString(() => FullscreenContainer.m_dst.Container)); + + EditorGUILayout.Space(); + } + + } + } + + private string SafeObjToString(Func obj) { + try { + var i = obj(); + return i == null? "null": i.ToString(); + } catch { + return "invalid"; + } + } + + } +} diff --git a/Assets/Fullscreen/Editor/PlaceholderWindow.cs.meta b/Assets/Fullscreen/Editor/PlaceholderWindow.cs.meta new file mode 100644 index 0000000..24b9a1e --- /dev/null +++ b/Assets/Fullscreen/Editor/PlaceholderWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88d5675826e7edf4e8642372ea3fe9da +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/PrefItem.cs b/Assets/Fullscreen/Editor/PrefItem.cs new file mode 100644 index 0000000..529a374 --- /dev/null +++ b/Assets/Fullscreen/Editor/PrefItem.cs @@ -0,0 +1,128 @@ +using System; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace FullscreenEditor { + /// Helper class for saving preferences. + /// The type you want to save, must be marked as + [Serializable] + public sealed class PrefItem { + + [SerializeField] + private T savedValue; + + /// The key for saving the value. + public string Key { get; private set; } + + /// The default value to use when there's none saved. + public T DefaultValue { get; private set; } + + /// A label and an explanation of what this item is for. + public GUIContent Content { get; private set; } + + /// Callback called whenever the saved value changes. + public Action OnValueSaved { get; set; } + + /// The value saved by this instance. + public T Value { + get { return savedValue; } + set { + if (!savedValue.Equals(value)) { + savedValue = value; + SaveValue(); + } + } + } + + public PrefItem(string key, T defaultValue, string text, string tooltip) { + Key = "Fullscreen." + key; + + FullscreenPreferences.onLoadDefaults += DeleteValue; + + Content = new GUIContent(text, tooltip); + FullscreenPreferences.contents.Add(Content); + DefaultValue = savedValue = defaultValue; + LoadValue(); + } + + private void LoadValue() { + try { + if (EditorPrefs.HasKey(Key)) + JsonUtility.FromJsonOverwrite(EditorPrefs.GetString(Key), this); + } catch (Exception e) { + Logger.Warning("Failed to load {0}, using default value: {1}", Key, e); + savedValue = DefaultValue; + SaveValue(); + } + } + + public void SaveValue() { + try { + EditorPrefs.SetString(Key, JsonUtility.ToJson(this)); + Logger.Debug("Saved value to key {0}:\n{1}", Key, EditorPrefs.GetString(Key)); + + if (OnValueSaved != null) + OnValueSaved.Invoke(Value); + + InternalEditorUtility.RepaintAllViews(); + } catch (Exception e) { + Logger.Warning("Failed to save {0}: {1}", Key, e); + } + } + + public void DeleteValue() { + EditorPrefs.DeleteKey(Key); + savedValue = DefaultValue; + + if (OnValueSaved != null) + OnValueSaved.Invoke(savedValue); + } + + public static implicit operator T(PrefItem pb) { return pb.Value; } + + public static implicit operator GUIContent(PrefItem pb) { return pb.Content; } + + } + + /// Helper class for drawing the in the . + public static class PrefItemGUI { + + public static void DoGUI(this PrefItem pref) { + pref.Value = EditorGUILayout.IntField(pref.Content, pref.Value); + } + + public static void DoGUI(this PrefItem pref) { + pref.Value = EditorGUILayout.FloatField(pref.Content, pref.Value); + } + + public static void DoGUI(this PrefItem pref, int min, int max) { + pref.Value = EditorGUILayout.IntSlider(pref.Content, pref.Value, min, max); + } + + public static void DoGUI(this PrefItem pref, float min, float max) { + pref.Value = EditorGUILayout.Slider(pref.Content, pref.Value, min, max); + } + + public static void DoGUI(this PrefItem pref) { + pref.Value = EditorGUILayout.Toggle(pref.Content, pref.Value); + } + + public static void DoGUI(this PrefItem pref) { + pref.Value = EditorGUILayout.TextField(pref.Content, pref.Value); + } + + public static void DoGUI(this PrefItem pref) { + pref.Value = EditorGUILayout.ColorField(pref.Content, pref.Value); + } + + public static void DoGUI(this PrefItem pref) { + pref.Value = EditorGUILayout.RectField(pref.Content, pref.Value); + } + + public static void DoGUI(this PrefItem pref) { + pref.Value = (RectSourceMode)EditorGUILayout.EnumPopup(pref.Content, pref.Value); + } + + } +} diff --git a/Assets/Fullscreen/Editor/PrefItem.cs.meta b/Assets/Fullscreen/Editor/PrefItem.cs.meta new file mode 100644 index 0000000..b5675db --- /dev/null +++ b/Assets/Fullscreen/Editor/PrefItem.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 4122d1a6d249e2845aa5f93eb83c2b50 +timeCreated: 1508428743 +licenseType: Store +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/ReflectionUtility.cs b/Assets/Fullscreen/Editor/ReflectionUtility.cs new file mode 100644 index 0000000..fa42e36 --- /dev/null +++ b/Assets/Fullscreen/Editor/ReflectionUtility.cs @@ -0,0 +1,197 @@ +using System; +using System.Linq; +using System.Reflection; +using UnityEditor; + +namespace FullscreenEditor { + /// Class containing method extensions for getting private and internal members. + public static class ReflectionUtility { + + private static Assembly[] cachedAssemblies; + + public const BindingFlags FULL_BINDING = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; + + /// Find a type by its name. + public static Type FindClass(string name) { + // return typeof(Editor).Assembly.GetType(name, false, true); + var result = FindTypeInAssembly(name, typeof(Editor).Assembly); + + if (result != null) + return result; + + if (cachedAssemblies == null) + cachedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + + for (var i = 0; i < cachedAssemblies.Length; i++) { + result = FindTypeInAssembly(name, cachedAssemblies[i]); + + if (result != null) + return result; + } + + return result; + } + + private static Type FindTypeInAssembly(string name, Assembly assembly) { + return assembly == null ? + null : + assembly.GetType(name, false, true); + } + + /// Find a field of a type by its name. + public static FieldInfo FindField(this Type type, string fieldName, bool throwNotFound = true) { + if (type == null) + throw new ArgumentNullException("type"); + + var field = type.GetField(fieldName, FULL_BINDING); + + if (field == null && throwNotFound) + throw new MissingFieldException(type.FullName, fieldName); + + return field; + } + + /// Find a property of a type by its name. + public static PropertyInfo FindProperty(this Type type, string propertyName, bool throwNotFound = true) { + if (type == null) + throw new ArgumentNullException("type"); + + var prop = type.GetProperty(propertyName, FULL_BINDING); + + if (prop == null && throwNotFound) + throw new MissingMemberException(type.FullName, propertyName); + + return prop; + } + + /// Find a method of a type by its name. + public static MethodInfo FindMethod(this Type type, string methodName, Type[] args = null, bool throwNotFound = true) { + if (type == null) + throw new ArgumentNullException("type"); + + MethodInfo method; + + if (args == null) { + method = type.GetMethod(methodName, FULL_BINDING); + // method = type.GetMethods(FULL_BINDING) + // .Where(m => m.Name == methodName) + // .FirstOrDefault(); + } else { + method = type.GetMethod(methodName, FULL_BINDING, null, args, null); + + // There are very specific cases where the above method may not bind properly + // e.g. when the method declares an enum and the arg type is an int, so we ignore the args + // and hope that there are no ambiguity of methods + if (method == null) { + method = FindMethod(type, methodName, null, throwNotFound); + + if (method != null && method.GetParameters().Length != args.Length) + method = null; + } + } + + if (method == null && throwNotFound) + throw new MissingMethodException(type.FullName, methodName); + + return method; + } + + /// Get the value of the static field. + public static T GetFieldValue(this Type type, string fieldName) { return (T)type.FindField(fieldName).GetValue(null); } + + /// Get the value of the instance field. + public static T GetFieldValue(this object obj, string fieldName) { return (T)obj.GetType().FindField(fieldName).GetValue(obj); } + + /// Set the value of the static field. + public static void SetFieldValue(this Type type, string fieldName, object value) { type.FindField(fieldName).SetValue(null, value); } + + /// Set the value of the instance field. + public static void SetFieldValue(this object obj, string fieldName, object value) { obj.GetType().FindField(fieldName).SetValue(obj, value); } + + /// Get the value of the static property. + public static T GetPropertyValue(this Type type, string propertyName) { return (T)type.FindProperty(propertyName).GetValue(null, null); } + + /// Get the value of the instance property. + public static T GetPropertyValue(this object obj, string propertyName) { return (T)obj.GetType().FindProperty(propertyName).GetValue(obj, null); } + + /// Set the value of the static property. + public static void SetPropertyValue(this Type type, string propertyName, object value) { type.FindProperty(propertyName).SetValue(null, value, null); } + + /// Set the value of the instance property. + public static void SetPropertyValue(this object obj, string propertyName, object value) { obj.GetType().FindProperty(propertyName).SetValue(obj, value, null); } + + /// Invoke a static method on the type and return the result. + public static T InvokeMethod(this Type type, string methodName, params object[] args) { return (T)type.FindMethod(methodName, args.Select(a => a.GetType()).ToArray()).Invoke(null, args); } + + /// Invoke a method on the object instance and return the result. + public static T InvokeMethod(this object obj, string methodName, params object[] args) { return (T)obj.GetType().FindMethod(methodName, args.Select(a => a.GetType()).ToArray()).Invoke(obj, args); } + + /// Invoke a static method on the type. + public static void InvokeMethod(this Type type, string methodName, params object[] args) { type.FindMethod(methodName, args.Select(a => a.GetType()).ToArray()).Invoke(null, args); } + + /// Invoke a method on the object instance. + public static void InvokeMethod(this object obj, string methodName, params object[] args) { obj.GetType().FindMethod(methodName, args.Select(a => a.GetType()).ToArray()).Invoke(obj, args); } + + /// Returns wheter the given type is the same as another one. + /// Type that will be checked. + /// Type to check against. + /// Returns true if the checked type is inherited from the type argument. + public static bool IsOfType(this Type toCheck, Type type, bool orInherited = true) { + return type == toCheck || (orInherited && type.IsAssignableFrom(toCheck)); + } + + /// Returns wheter the given instance is of a given type. + /// The instance to check. + /// Type to check against. + /// Returns true if the instance is inherited from the type argument. + public static bool IsOfType(this T obj, Type type, bool orInherited = true) { + return obj.GetType().IsOfType(type, orInherited); + } + + /// Throws an exception if the instance is not of the given type. + /// The instance to check. + /// Type to check against. + /// Do not throw if the instance is inherited from the type argument. + public static void EnsureOfType(this T obj, Type type, bool orInherited = true) { + if (!obj.IsOfType(type, orInherited)) + throw new InvalidCastException( + string.Format("Object {0} must be of type {1}{2}", + obj.GetType().FullName, + type.FullName, + orInherited? " or inherited from it": "" + ) + ); + } + + /// Returns whether the type defines the static field. + public static bool HasField(this Type type, string fieldName) { + return type.FindField(fieldName, false) != null; + } + + /// Returns whether the type defines the static property. + public static bool HasProperty(this Type type, string propertyName) { + return type.FindProperty(propertyName, false) != null; + } + + /// Returns whether the type defines the static method. + public static bool HasMethod(this Type type, string methodName, Type[] args = null) { + return type.FindMethod(methodName, args, false) != null; + } + + /// Returns whether the object type defines the instance field. + public static bool HasField(this object obj, string fieldName) { + return obj.GetType().HasField(fieldName); + } + + /// Returns whether the object type defines the instance property. + public static bool HasProperty(this object obj, string propertyName) { + return obj.GetType().HasProperty(propertyName); + } + + /// Returns whether the object type defines the instance method. + public static bool HasMethod(this object obj, string methodName, Type[] args = null) { + return obj.GetType().HasMethod(methodName, args); + } + + } +} diff --git a/Assets/Fullscreen/Editor/ReflectionUtility.cs.meta b/Assets/Fullscreen/Editor/ReflectionUtility.cs.meta new file mode 100644 index 0000000..96bf3ab --- /dev/null +++ b/Assets/Fullscreen/Editor/ReflectionUtility.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: bf03c2f7a14d3bf4da513b03829e3157 +timeCreated: 1508968711 +licenseType: Store +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/RestoreCursorState.cs b/Assets/Fullscreen/Editor/RestoreCursorState.cs new file mode 100644 index 0000000..c9c1d3c --- /dev/null +++ b/Assets/Fullscreen/Editor/RestoreCursorState.cs @@ -0,0 +1,43 @@ +using System; +using UnityEditor; +using UnityEngine; + +namespace FullscreenEditor { + [InitializeOnLoad] + public class RestoreCursorState { + + static RestoreCursorState() { + var cursorVisible = Cursor.visible; + + // this is where the magic happens + Action magic = (fs) => { + if (!FullscreenPreferences.RestoreCursorLockAndHideState) return; + + // frame count doesn't seem to make much of a difference, + // but I think it's best to do this after the view is + // focused by the "FixGameViewMouseInput" class + After.Frames(55, () => { + var gameView = fs.m_dst.Window && fs.m_dst.Window.IsOfType(Types.GameView) ? fs.m_dst.Window : + fs.m_src.Window && fs.m_src.Window.IsOfType(Types.GameView) ? fs.m_src.Window : null; + + if (!EditorApplication.isPaused && gameView && gameView.IsOfType(Types.GameView) && gameView.HasMethod("AllowCursorLockAndHide")) { + gameView.InvokeMethod("AllowCursorLockAndHide", true); + Unsupported.SetAllowCursorHide(true); + Cursor.visible = cursorVisible; + } + }); + }; + + Action storeCursorVisible = (fs) => { + cursorVisible = Cursor.visible; + }; + + FullscreenCallbacks.afterFullscreenOpen += magic; + FullscreenCallbacks.afterFullscreenClose += magic; + + FullscreenCallbacks.beforeFullscreenOpen += storeCursorVisible; + FullscreenCallbacks.beforeFullscreenClose += storeCursorVisible; + } + + } +} diff --git a/Assets/Fullscreen/Editor/RestoreCursorState.cs.meta b/Assets/Fullscreen/Editor/RestoreCursorState.cs.meta new file mode 100644 index 0000000..6a13eb7 --- /dev/null +++ b/Assets/Fullscreen/Editor/RestoreCursorState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dff90cc9864f9c041a937e8fd130ee9c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Shortcut.cs b/Assets/Fullscreen/Editor/Shortcut.cs new file mode 100644 index 0000000..081a8f4 --- /dev/null +++ b/Assets/Fullscreen/Editor/Shortcut.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; +using Debug = UnityEngine.Debug; + +namespace FullscreenEditor { + + [AttributeUsage(AttributeTargets.Field)] + internal class DynamicMenuItemAttribute : Attribute { + + public bool AllowNoneValue { get; private set; } + + public DynamicMenuItemAttribute(bool allowNoneValue) { + AllowNoneValue = allowNoneValue; + } + + } + + internal class Shortcut { + + #region Fields + //Always end with an space if the path has no shortcut + [DynamicMenuItem(true)] public const string TOOLBAR_PATH = "Fullscreen/Show Toolbar _F8"; + [DynamicMenuItem(true)] public const string FULLSCREEN_ON_PLAY_PATH = "Fullscreen/Fullscreen On Play "; + [DynamicMenuItem(true)] public const string PREFERENCES_PATH = "Fullscreen/Preferences... "; + [DynamicMenuItem(false)] public const string CURRENT_VIEW_PATH = "Fullscreen/Focused View _F9"; + [DynamicMenuItem(false)] public const string GAME_VIEW_PATH = "Fullscreen/Game View _F10"; + [DynamicMenuItem(false)] public const string SCENE_VIEW_PATH = "Fullscreen/Scene View _F11"; + [DynamicMenuItem(false)] public const string MAIN_VIEW_PATH = "Fullscreen/Main View _F12"; + [DynamicMenuItem(false)] public const string MOSAIC_PATH = "Fullscreen/Mosaic %F10"; + [DynamicMenuItem(true)] public const string CLOSE_ALL_FULLSCREEN = "Fullscreen/Close All %F12"; + + private const char CTRL_CHAR = '%'; + private const char SHIFT_CHAR = '#'; + private const char ALT_CHAR = '&'; + private const char NONE_CHAR = '_'; + + private static readonly List fieldsInfo = new List(); + /* fixformat ignore:start */ + private static readonly string[] keys = new string[] { + "None", + "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "LEFT", "RIGHT", "UP", "DOWN", "HOME", "END", "PGUP", "PGDN" + }; + /* fixformat ignore:end */ + + private static bool changed; + #endregion + + #region Properties + public bool Ctrl { get; set; } + public bool Shift { get; set; } + public bool Alt { get; set; } + public int KeyCode { get; set; } + + public bool AllowNoneValue { get; private set; } + public string FieldName { get; private set; } + public string BaseString { get; private set; } + public string Label { get { return BaseString.Substring(BaseString.LastIndexOf('/') + 1); } } + + private static bool IsSourceFile { get { return !string.IsNullOrEmpty(ThisFilePath) && File.Exists(ThisFilePath); } } + private static string ThisFilePath { + get { + try { + return new StackFrame(true).GetFileName(); + } catch (Exception e) { + Logger.Exception(e); + return string.Empty; + } + } + } + #endregion + + #region Constructors + static Shortcut() { + var type = typeof(Shortcut); + var fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); + + if (fields != null) + foreach (var field in fields) { + var att = field.GetCustomAttributes(typeof(DynamicMenuItemAttribute), false); + + if (att != null) + for (var i = 0; i < att.Length; i++) + fieldsInfo.Add(new Shortcut((DynamicMenuItemAttribute)att[i], field)); + } + } + + public Shortcut(DynamicMenuItemAttribute shortcutAttribute, FieldInfo field) { + FieldName = field.Name; + AllowNoneValue = shortcutAttribute.AllowNoneValue; + + var constant = (string)field.GetValue(null); + var lastSpace = constant.LastIndexOf(' ') + 1; + + if (!constant.EndsWith(" ")) + BaseString = constant.Remove(lastSpace); + else { + BaseString = constant; + return; + } + + constant = constant.Substring(lastSpace); + + if (string.IsNullOrEmpty(constant)) + return; + + Ctrl = constant.Contains(CTRL_CHAR); + Shift = constant.Contains(SHIFT_CHAR); + Alt = constant.Contains(ALT_CHAR); + + constant = constant.Replace(CTRL_CHAR.ToString(), string.Empty); + constant = constant.Replace(SHIFT_CHAR.ToString(), string.Empty); + constant = constant.Replace(ALT_CHAR.ToString(), string.Empty); + constant = constant.Replace(NONE_CHAR.ToString(), string.Empty); + + KeyCode = Array.IndexOf(keys, constant); + + if (KeyCode < 0 || KeyCode >= keys.Length) { + Logger.Warning("Invalid shortcut term: {0}", constant); + KeyCode = 0; + } + } + #endregion + + public string GetShortcutString() { + if (KeyCode == 0) + return ""; + + var result = new StringBuilder(); + + if (!Ctrl && !Shift && !Alt) + result.Append(NONE_CHAR); + else { + if (Ctrl) + result.Append(CTRL_CHAR); + if (Shift) + result.Append(SHIFT_CHAR); + if (Alt) + result.Append(ALT_CHAR); + } + + result.Append(keys[KeyCode]); + + return result.ToString(); + } + + #region Methods + public override string ToString() { + return BaseString + GetShortcutString(); + } + + public static void DoShortcutsGUI() { + GUI.changed = false; + + using(new EditorGUI.DisabledGroupScope(EditorApplication.isCompiling || !IsSourceFile)) { + + if (InternalEditorUtility.GetUnityVersion() >= new Version(2019, 1)) + EditorGUILayout.HelpBox(string.Format("You can set custom shortcuts on a per user basis by editing them under {0} menu", FullscreenUtility.IsMacOS ? "Unity/Shortcuts" : "Edit/Shortcuts"), MessageType.Info); + + foreach (var field in fieldsInfo) + DrawShortcut(field); + + var duplicated = AnyDuplicates(); + var invalid = AnyInvalid(); + + if (duplicated) + EditorGUILayout.HelpBox("Some menu items have the same keystroke, this is not allowed.", MessageType.Error); + + if (invalid) + EditorGUILayout.HelpBox("Some menu items don't have a valid keystroke, you won't be able to use their correspondent fullscreens.", MessageType.Warning); + + using(new EditorGUI.DisabledGroupScope(duplicated || !changed)) + if (GUILayout.Button("Apply Shortcuts")) + ApplyChanges(); + } + + if (GUI.changed) + changed = true; + } + + private static void ApplyChanges() { + if (EditorApplication.isCompiling) + return; + + AssetDatabase.StartAssetEditing(); + + foreach (var field in fieldsInfo) + ReplaceConstant(field.FieldName, field); + + AssetDatabase.StopAssetEditing(); + AssetDatabase.Refresh(); + } + + private static bool AnyInvalid() { + foreach (var field in fieldsInfo) + if (field == null || !field.AllowNoneValue && field.KeyCode == 0) + return true; + + return false; + } + + private static bool AnyDuplicates() { + for (var i = 0; i < fieldsInfo.Count; i++) + for (var j = i + 1; j < fieldsInfo.Count; j++) { + var fieldI = fieldsInfo[i]; + var fieldJ = fieldsInfo[j]; + + if (fieldI == null || fieldJ == null || + (fieldI.KeyCode != 0 && fieldI.GetShortcutString() == fieldJ.GetShortcutString())) + return true; + } + + return false; + } + + private static Shortcut DrawShortcut(Shortcut shortcut) { + using(new EditorGUILayout.HorizontalScope()) { + EditorGUILayout.LabelField(shortcut.Label, GUILayout.Width(130f)); + + shortcut.Ctrl = GUILayout.Toggle(shortcut.Ctrl, FullscreenUtility.IsMacOS ? "Cmd" : "Ctrl", EditorStyles.miniButtonLeft, GUILayout.Width(50f)); + shortcut.Shift = GUILayout.Toggle(shortcut.Shift, "Shift", EditorStyles.miniButtonMid, GUILayout.Width(50f)); + shortcut.Alt = GUILayout.Toggle(shortcut.Alt, "Alt", EditorStyles.miniButtonRight, GUILayout.Width(50f)); + shortcut.KeyCode = EditorGUILayout.Popup(shortcut.KeyCode, keys); + + if (GUILayout.Button(new GUIContent("X", "Clear Shortcut"))) { + shortcut.Ctrl = false; + shortcut.Shift = false; + shortcut.Alt = false; + shortcut.KeyCode = 0; + } + } + + return shortcut; + } + + private static void ReplaceConstant(string constantName, object newValue) { + try { + if (!IsSourceFile) { + Logger.Error("Could not find the source code file to change value"); + return; + } + + var fileText = new StringBuilder(); + var changed = false; + + using(var file = File.OpenText(ThisFilePath)) + while (!file.EndOfStream) { + var line = file.ReadLine(); + + if (!line.Contains(constantName)) { + fileText.AppendLine(line); + continue; + } + + var indexOfValue = line.IndexOf('='); + + fileText.Append(line.Remove(indexOfValue)); + fileText.AppendLine(string.Format("= \"{0}\";", newValue)); + fileText.Append(file.ReadToEnd()); + + changed = true; + } + + fileText = fileText.Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", Environment.NewLine); + + if (changed) + File.WriteAllText(ThisFilePath, fileText.ToString()); + else + Logger.Warning("Failed to find field {0} on {1}", constantName, ThisFilePath); + } catch (Exception e) { + Logger.Exception(e); + Logger.Error("Failed to save Fullscreen Editor shortcuts"); + } + } + #endregion + + } + +} diff --git a/Assets/Fullscreen/Editor/Shortcut.cs.meta b/Assets/Fullscreen/Editor/Shortcut.cs.meta new file mode 100644 index 0000000..acad4a1 --- /dev/null +++ b/Assets/Fullscreen/Editor/Shortcut.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 88013eba7392a9e418e55e3229a5f144 +timeCreated: 1508377869 +licenseType: Store +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/ViewPyramid.cs b/Assets/Fullscreen/Editor/ViewPyramid.cs new file mode 100644 index 0000000..cffe48a --- /dev/null +++ b/Assets/Fullscreen/Editor/ViewPyramid.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityObject = UnityEngine.Object; +using HostView = UnityEngine.ScriptableObject; +using View = UnityEngine.ScriptableObject; +using ContainerWindow = UnityEngine.ScriptableObject; + +namespace FullscreenEditor { + /// Represents the pyramid containing all the elements that make up a window. + [Serializable] + public struct ViewPyramid { + + /// The actual window, may be null if the pyramid was created from a view or container. + public EditorWindow Window { + get { + if (!m_window && m_windowInstanceID != 0) + m_window = (EditorWindow)EditorUtility.InstanceIDToObject(m_windowInstanceID); + return m_window; + } + set { + m_window = value; + m_windowInstanceID = m_window ? m_window.GetInstanceID() : 0; + } + } + + /// View that controls how the window (and child view) are drawn. + public View View { + get { + if (!m_view && m_viewInstanceID != 0) + m_view = (View)EditorUtility.InstanceIDToObject(m_viewInstanceID); + return m_view; + } + set { + value.EnsureOfType(Types.View); + m_view = value; + m_viewInstanceID = m_view ? m_view.GetInstanceID() : 0; + } + } + + /// The native window. + public ContainerWindow Container { + get { + if (!m_container && m_containerInstanceID != 0) + m_container = (ContainerWindow)EditorUtility.InstanceIDToObject(m_containerInstanceID); + return m_container; + } + set { + value.EnsureOfType(Types.ContainerWindow); + m_container = value; + m_containerInstanceID = m_container ? m_container.GetInstanceID() : 0; + } + } + + [SerializeField] private EditorWindow m_window; + [SerializeField] private View m_view; + [SerializeField] private ContainerWindow m_container; + + [SerializeField] private int m_windowInstanceID; + [SerializeField] private int m_viewInstanceID; + [SerializeField] private int m_containerInstanceID; + + /// Create a new instance and automatically assigns the window, view and container. + public ViewPyramid(ScriptableObject viewOrWindow) { + + if (!viewOrWindow) { + m_window = null; + m_view = null; + m_container = null; + } else if (viewOrWindow.IsOfType(typeof(EditorWindow))) { + m_window = viewOrWindow as EditorWindow; + m_view = m_window.GetFieldValue("m_Parent"); + m_container = m_view.GetPropertyValue("window"); + } else if (viewOrWindow.IsOfType(Types.View)) { + m_window = null; + m_view = viewOrWindow; + m_container = m_view.GetPropertyValue("window"); + } else if (viewOrWindow.IsOfType(Types.ContainerWindow)) { + m_window = null; + m_view = viewOrWindow.GetPropertyValue("rootView"); + m_container = viewOrWindow; + } else { + throw new ArgumentException("Param must be of type EditorWindow, View or ContainerWindow", "viewOrWindow"); + } + + if (!m_window && m_view && m_view.IsOfType(Types.HostView)) + m_window = m_view.GetPropertyValue("actualView"); + + m_windowInstanceID = m_window ? m_window.GetInstanceID() : 0; + m_viewInstanceID = m_view ? m_view.GetInstanceID() : 0; + m_containerInstanceID = m_container ? m_container.GetInstanceID() : 0; + + } + + } +} diff --git a/Assets/Fullscreen/Editor/ViewPyramid.cs.meta b/Assets/Fullscreen/Editor/ViewPyramid.cs.meta new file mode 100644 index 0000000..a621740 --- /dev/null +++ b/Assets/Fullscreen/Editor/ViewPyramid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 26219245258051b44a2dc559b5e5584f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Windows.meta b/Assets/Fullscreen/Editor/Windows.meta new file mode 100644 index 0000000..7e03954 --- /dev/null +++ b/Assets/Fullscreen/Editor/Windows.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ad11a5b026b2b8244849852d5718d291 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Windows/GDI32.cs b/Assets/Fullscreen/Editor/Windows/GDI32.cs new file mode 100644 index 0000000..46af995 --- /dev/null +++ b/Assets/Fullscreen/Editor/Windows/GDI32.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.InteropServices; + +namespace FullscreenEditor.Windows { + internal static class GDI32 { + + // http://pinvoke.net/default.aspx/gdi32/GetDeviceCaps.html + public enum DeviceCap { + VERTRES = 10, + DESKTOPVERTRES = 117, + } + + [DllImport("gdi32.dll")] + public static extern int GetDeviceCaps(IntPtr hDc, int nIndex); + + } +} diff --git a/Assets/Fullscreen/Editor/Windows/GDI32.cs.meta b/Assets/Fullscreen/Editor/Windows/GDI32.cs.meta new file mode 100644 index 0000000..e692ddf --- /dev/null +++ b/Assets/Fullscreen/Editor/Windows/GDI32.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d2e63aa031b326e4c932e1015ea72da1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Windows/ShCore.cs b/Assets/Fullscreen/Editor/Windows/ShCore.cs new file mode 100644 index 0000000..7e0b699 --- /dev/null +++ b/Assets/Fullscreen/Editor/Windows/ShCore.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; + +namespace FullscreenEditor.Windows { + + internal enum MonitorDpiType { + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + } + + internal static class ShCore { + + [DllImport("shcore.dll")] + internal static extern uint GetDpiForMonitor(IntPtr hmonitor, MonitorDpiType dpiType, out uint dpiX, out uint dpiY); + + } +} diff --git a/Assets/Fullscreen/Editor/Windows/ShCore.cs.meta b/Assets/Fullscreen/Editor/Windows/ShCore.cs.meta new file mode 100644 index 0000000..edc93d4 --- /dev/null +++ b/Assets/Fullscreen/Editor/Windows/ShCore.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 452ce41cefb917e42b0f681115e84f84 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Windows/Structs.cs b/Assets/Fullscreen/Editor/Windows/Structs.cs new file mode 100644 index 0000000..e630352 --- /dev/null +++ b/Assets/Fullscreen/Editor/Windows/Structs.cs @@ -0,0 +1,127 @@ +using System; +using System.Runtime.InteropServices; +using UnityEngine; + +namespace FullscreenEditor.Windows { + [System.Serializable] + [StructLayout(LayoutKind.Sequential)] + internal struct NativeRect { + public int left; + public int top; + public int right; + public int bottom; + + public static implicit operator Rect(NativeRect other) { + return Rect.MinMaxRect( + other.left, + other.top, + other.right, + other.bottom + ); + } + + public static implicit operator NativeRect(Rect other) { + return new NativeRect { + left = (int)other.xMin, + top = (int)other.yMin, + right = (int)other.xMax, + bottom = (int)other.yMax + }; + } + + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + internal struct MonitorInfoEx { + + private const int CCHDEVICENAME = 0x20; + + public int size; + public NativeRect monitor; + public NativeRect work; + public uint flags; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)] + public string DeviceName; + + public void Init() { + this.size = 40 + 1 * CCHDEVICENAME; + this.DeviceName = string.Empty; + } + + } + + [System.Serializable] + [StructLayout(LayoutKind.Sequential)] + internal struct DevMode { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] + public string dmDeviceName; + public short dmSpecVersion; + public short dmDriverVersion; + public short dmSize; + public short dmDriverExtra; + public int dmFields; + public int dmPositionX; + public int dmPositionY; + public ScreenOrientation dmDisplayOrientation; + public int dmDisplayFixedOutput; + public short dmColor; + public short dmDuplex; + public short dmYResolution; + public short dmTTOption; + public short dmCollate; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] + public string dmFormName; + public short dmLogPixels; + public int dmBitsPerPel; + public int dmPelsWidth; + public int dmPelsHeight; + public int dmDisplayFlags; + public int dmDisplayFrequency; + public int dmICMMethod; + public int dmICMIntent; + public int dmMediaType; + public int dmDitherType; + public int dmReserved1; + public int dmReserved2; + public int dmPanningWidth; + public int dmPanningHeight; + } + + [Flags] + internal enum DisplayDeviceStateFlags : int { + /// The device is part of the desktop. + AttachedToDesktop = 0x1, + MultiDriver = 0x2, + /// The device is part of the desktop. + PrimaryDevice = 0x4, + /// Represents a pseudo device used to mirror application drawing for remoting or other purposes. + MirroringDriver = 0x8, + /// The device is VGA compatible. + VGACompatible = 0x10, + /// The device is removable; it cannot be the primary display. + Removable = 0x20, + /// The device has more display modes than its output devices support. + ModesPruned = 0x8000000, + Remote = 0x4000000, + Disconnect = 0x2000000 + } + + [System.Serializable] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + internal struct DisplayDevice { + [MarshalAs(UnmanagedType.U4)] + public int cb; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string DeviceName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string DeviceString; + [MarshalAs(UnmanagedType.U4)] + public DisplayDeviceStateFlags StateFlags; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string DeviceID; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string DeviceKey; + } + +} diff --git a/Assets/Fullscreen/Editor/Windows/Structs.cs.meta b/Assets/Fullscreen/Editor/Windows/Structs.cs.meta new file mode 100644 index 0000000..7df08e8 --- /dev/null +++ b/Assets/Fullscreen/Editor/Windows/Structs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd1767e9696fe0a4ca0d7d06b1384272 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Editor/Windows/User32.cs b/Assets/Fullscreen/Editor/Windows/User32.cs new file mode 100644 index 0000000..67917e1 --- /dev/null +++ b/Assets/Fullscreen/Editor/Windows/User32.cs @@ -0,0 +1,32 @@ +using System; +using System.Runtime.InteropServices; + +namespace FullscreenEditor.Windows { + + internal static class User32 { + + public delegate bool EnumMonitorsDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref NativeRect lprcMonitor, IntPtr dwData); + + [DllImport("user32.dll")] + public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, EnumMonitorsDelegate lpfnEnum, IntPtr dwData); + + [DllImport("user32.dll")] + public static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfoEx lpmi); + + [DllImport("user32.dll")] + public static extern bool EnumDisplaySettings(string deviceName, int modeNum, ref DevMode devMode); + + [DllImport("user32.dll")] + public static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DisplayDevice lpDisplayDevice, uint dwFlags); + + [DllImport("user32.dll")] + public static extern IntPtr GetDC(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDc); + + [DllImport("user32.dll", SetLastError = true)] + public static extern int GetSystemMetrics(int smIndex); + + } +} diff --git a/Assets/Fullscreen/Editor/Windows/User32.cs.meta b/Assets/Fullscreen/Editor/Windows/User32.cs.meta new file mode 100644 index 0000000..6d1a9e7 --- /dev/null +++ b/Assets/Fullscreen/Editor/Windows/User32.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2d1661fb151eb4d48a4dc4e4294a3351 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/FullscreenEditor.asmdef b/Assets/Fullscreen/FullscreenEditor.asmdef new file mode 100644 index 0000000..bd06980 --- /dev/null +++ b/Assets/Fullscreen/FullscreenEditor.asmdef @@ -0,0 +1,15 @@ +{ + "name": "FullscreenEditor", + "references": [], + "optionalUnityReferences": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [] +} \ No newline at end of file diff --git a/Assets/Fullscreen/FullscreenEditor.asmdef.meta b/Assets/Fullscreen/FullscreenEditor.asmdef.meta new file mode 100644 index 0000000..5652664 --- /dev/null +++ b/Assets/Fullscreen/FullscreenEditor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 838d3286f0973344ab6e99d3951012f7 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Fullscreen/Readme.pdf b/Assets/Fullscreen/Readme.pdf new file mode 100644 index 0000000..c02fa8e Binary files /dev/null and b/Assets/Fullscreen/Readme.pdf differ diff --git a/Assets/Fullscreen/Readme.pdf.meta b/Assets/Fullscreen/Readme.pdf.meta new file mode 100644 index 0000000..97c083f --- /dev/null +++ b/Assets/Fullscreen/Readme.pdf.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 0c3e30c36dfe6484c88009ed994d43a6 +timeCreated: 1510520595 +licenseType: Store +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/HDRP/Act-4/Act4.asset b/Assets/HDRP/Act-4/Act4.asset index e39fac0..91ea546 100644 --- a/Assets/HDRP/Act-4/Act4.asset +++ b/Assets/HDRP/Act-4/Act4.asset @@ -208,6 +208,40 @@ MonoBehaviour: m_SampleCount: m_OverrideState: 0 m_Value: 2 +--- !u!114 &-1453259281000056494 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6b24b200358312b4fa1004e2431c2f1f, type: 3} + m_Name: ShadowsMidtonesHighlights + m_EditorClassIdentifier: + active: 1 + shadows: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: -0.41681257} + midtones: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0.22767073} + highlights: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 1} + shadowsStart: + m_OverrideState: 1 + m_Value: 0 + shadowsEnd: + m_OverrideState: 1 + m_Value: 0.3 + highlightsStart: + m_OverrideState: 1 + m_Value: 0.55 + highlightsEnd: + m_OverrideState: 1 + m_Value: 1 --- !u!114 &11400000 MonoBehaviour: m_ObjectHideFlags: 0 @@ -231,6 +265,8 @@ MonoBehaviour: - {fileID: -9013598382846220588} - {fileID: 436855201865411411} - {fileID: 5947967167066595232} + - {fileID: 2351996697071554633} + - {fileID: -1453259281000056494} --- !u!114 &436855201865411411 MonoBehaviour: m_ObjectHideFlags: 3 @@ -278,6 +314,87 @@ MonoBehaviour: fogType: m_OverrideState: 1 m_Value: 0 +--- !u!114 &2351996697071554633 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9008a067f4d626c4d8bc4bc48f04bb89, type: 3} + m_Name: ScreenSpaceAmbientOcclusion + m_EditorClassIdentifier: + active: 1 + quality: + m_OverrideState: 1 + m_Value: 1 + rayTracing: + m_OverrideState: 1 + m_Value: 0 + intensity: + m_OverrideState: 1 + m_Value: 1 + directLightingStrength: + m_OverrideState: 1 + m_Value: 0 + radius: + m_OverrideState: 1 + m_Value: 2 + spatialBilateralAggressiveness: + m_OverrideState: 1 + m_Value: 0.15 + temporalAccumulation: + m_OverrideState: 1 + m_Value: 1 + ghostingReduction: + m_OverrideState: 1 + m_Value: 1 + blurSharpness: + m_OverrideState: 1 + m_Value: 0.1 + layerMask: + m_OverrideState: 1 + m_Value: + serializedVersion: 2 + m_Bits: 4294967295 + specularOcclusion: + m_OverrideState: 0 + m_Value: 0.5 + occluderMotionRejection: + m_OverrideState: 0 + m_Value: 1 + receiverMotionRejection: + m_OverrideState: 0 + m_Value: 1 + m_StepCount: + m_OverrideState: 1 + m_Value: 6 + m_FullResolution: + m_OverrideState: 1 + m_Value: 0 + m_MaximumRadiusInPixels: + m_OverrideState: 1 + m_Value: 40 + m_BilateralUpsample: + m_OverrideState: 1 + m_Value: 1 + m_DirectionCount: + m_OverrideState: 1 + m_Value: 2 + m_RayLength: + m_OverrideState: 1 + m_Value: 3 + m_SampleCount: + m_OverrideState: 1 + m_Value: 2 + m_Denoise: + m_OverrideState: 1 + m_Value: 1 + m_DenoiserRadius: + m_OverrideState: 1 + m_Value: 0.5 --- !u!114 &3069685883771409258 MonoBehaviour: m_ObjectHideFlags: 3 @@ -308,7 +425,7 @@ MonoBehaviour: m_Value: {r: 1, g: 1, b: 1, a: 1} maxFogDistance: m_OverrideState: 1 - m_Value: 5000 + m_Value: 2 mipFogMaxMip: m_OverrideState: 0 m_Value: 0.5 @@ -433,7 +550,7 @@ MonoBehaviour: m_Value: 1 length: m_OverrideState: 1 - m_Value: 0.15 + m_Value: 0.497 opacity: m_OverrideState: 1 m_Value: 1 @@ -485,7 +602,7 @@ MonoBehaviour: m_Value: 1 fixedExposure: m_OverrideState: 1 - m_Value: 0.9244535 + m_Value: 0.6111921 compensation: m_OverrideState: 1 m_Value: 0 @@ -648,16 +765,16 @@ MonoBehaviour: m_Value: 128 m_DenoiseSS: m_OverrideState: 1 - m_Value: 0 + m_Value: 1 m_HalfResolutionDenoiserSS: m_OverrideState: 1 - m_Value: 0 + m_Value: 1 m_DenoiserRadiusSS: m_OverrideState: 1 m_Value: 0.5 m_SecondDenoiserPassSS: m_OverrideState: 1 - m_Value: 0 + m_Value: 1 lastBounceFallbackHierarchy: m_OverrideState: 0 m_Value: 3