Browse Source

pre-osc

post-projects
Cailean Finn 12 months ago
parent
commit
f7df6d2016
  1. 38
      Assets/Act-4 Azure.unity
  2. 9
      Assets/Fullscreen.meta
  3. 9
      Assets/Fullscreen/Editor.meta
  4. 52
      Assets/Fullscreen/Editor/After.cs
  5. 11
      Assets/Fullscreen/Editor/After.cs.meta
  6. 90
      Assets/Fullscreen/Editor/Cmd.cs
  7. 11
      Assets/Fullscreen/Editor/Cmd.cs.meta
  8. 151
      Assets/Fullscreen/Editor/DisableSceneView.cs
  9. 11
      Assets/Fullscreen/Editor/DisableSceneView.cs.meta
  10. 8
      Assets/Fullscreen/Editor/Displays.meta
  11. 184
      Assets/Fullscreen/Editor/Displays/DisplayInfo.windows.cs
  12. 11
      Assets/Fullscreen/Editor/Displays/DisplayInfo.windows.cs.meta
  13. 109
      Assets/Fullscreen/Editor/Disposables.cs
  14. 11
      Assets/Fullscreen/Editor/Disposables.cs.meta
  15. 26
      Assets/Fullscreen/Editor/FixGameViewMouseInput.cs
  16. 11
      Assets/Fullscreen/Editor/FixGameViewMouseInput.cs.meta
  17. 166
      Assets/Fullscreen/Editor/Fullscreen.cs
  18. 13
      Assets/Fullscreen/Editor/Fullscreen.cs.meta
  19. 32
      Assets/Fullscreen/Editor/FullscreenCallbacks.cs
  20. 11
      Assets/Fullscreen/Editor/FullscreenCallbacks.cs.meta
  21. 135
      Assets/Fullscreen/Editor/FullscreenContainer.cs
  22. 11
      Assets/Fullscreen/Editor/FullscreenContainer.cs.meta
  23. 114
      Assets/Fullscreen/Editor/FullscreenContainerInternal.cs
  24. 11
      Assets/Fullscreen/Editor/FullscreenContainerInternal.cs.meta
  25. 121
      Assets/Fullscreen/Editor/FullscreenOnPlay.cs
  26. 13
      Assets/Fullscreen/Editor/FullscreenOnPlay.cs.meta
  27. 409
      Assets/Fullscreen/Editor/FullscreenPreferences.cs
  28. 13
      Assets/Fullscreen/Editor/FullscreenPreferences.cs.meta
  29. 193
      Assets/Fullscreen/Editor/FullscreenRects.cs
  30. 13
      Assets/Fullscreen/Editor/FullscreenRects.cs.meta
  31. 432
      Assets/Fullscreen/Editor/FullscreenUtility.cs
  32. 13
      Assets/Fullscreen/Editor/FullscreenUtility.cs.meta
  33. 63
      Assets/Fullscreen/Editor/FullscreenView.cs
  34. 11
      Assets/Fullscreen/Editor/FullscreenView.cs.meta
  35. 234
      Assets/Fullscreen/Editor/FullscreenWindow.cs
  36. 11
      Assets/Fullscreen/Editor/FullscreenWindow.cs.meta
  37. 18
      Assets/Fullscreen/Editor/GameViewLowResolutionAspectRatios.cs
  38. 7
      Assets/Fullscreen/Editor/GameViewLowResolutionAspectRatios.cs.meta
  39. 35
      Assets/Fullscreen/Editor/GameViewVSync.cs
  40. 11
      Assets/Fullscreen/Editor/GameViewVSync.cs.meta
  41. 41
      Assets/Fullscreen/Editor/GlobalToolbarHiding.cs
  42. 11
      Assets/Fullscreen/Editor/GlobalToolbarHiding.cs.meta
  43. 81
      Assets/Fullscreen/Editor/Integration.cs
  44. 11
      Assets/Fullscreen/Editor/Integration.cs.meta
  45. 42
      Assets/Fullscreen/Editor/InternalTypes.cs
  46. 11
      Assets/Fullscreen/Editor/InternalTypes.cs.meta
  47. 65
      Assets/Fullscreen/Editor/KeepFullscreenBelow.cs
  48. 11
      Assets/Fullscreen/Editor/KeepFullscreenBelow.cs.meta
  49. 8
      Assets/Fullscreen/Editor/Linux.meta
  50. 22
      Assets/Fullscreen/Editor/Linux/NativeFullscreenHook.cs
  51. 11
      Assets/Fullscreen/Editor/Linux/NativeFullscreenHook.cs.meta
  52. 75
      Assets/Fullscreen/Editor/Linux/wmctrl.cs
  53. 11
      Assets/Fullscreen/Editor/Linux/wmctrl.cs.meta
  54. 56
      Assets/Fullscreen/Editor/Logger.cs
  55. 11
      Assets/Fullscreen/Editor/Logger.cs.meta
  56. 176
      Assets/Fullscreen/Editor/MenuItems.cs
  57. 13
      Assets/Fullscreen/Editor/MenuItems.cs.meta
  58. 137
      Assets/Fullscreen/Editor/Patcher.cs
  59. 11
      Assets/Fullscreen/Editor/Patcher.cs.meta
  60. 174
      Assets/Fullscreen/Editor/PlaceholderWindow.cs
  61. 11
      Assets/Fullscreen/Editor/PlaceholderWindow.cs.meta
  62. 128
      Assets/Fullscreen/Editor/PrefItem.cs
  63. 13
      Assets/Fullscreen/Editor/PrefItem.cs.meta
  64. 197
      Assets/Fullscreen/Editor/ReflectionUtility.cs
  65. 13
      Assets/Fullscreen/Editor/ReflectionUtility.cs.meta
  66. 43
      Assets/Fullscreen/Editor/RestoreCursorState.cs
  67. 11
      Assets/Fullscreen/Editor/RestoreCursorState.cs.meta
  68. 289
      Assets/Fullscreen/Editor/Shortcut.cs
  69. 13
      Assets/Fullscreen/Editor/Shortcut.cs.meta
  70. 98
      Assets/Fullscreen/Editor/ViewPyramid.cs
  71. 11
      Assets/Fullscreen/Editor/ViewPyramid.cs.meta
  72. 8
      Assets/Fullscreen/Editor/Windows.meta
  73. 17
      Assets/Fullscreen/Editor/Windows/GDI32.cs
  74. 11
      Assets/Fullscreen/Editor/Windows/GDI32.cs.meta
  75. 18
      Assets/Fullscreen/Editor/Windows/ShCore.cs
  76. 11
      Assets/Fullscreen/Editor/Windows/ShCore.cs.meta
  77. 127
      Assets/Fullscreen/Editor/Windows/Structs.cs
  78. 11
      Assets/Fullscreen/Editor/Windows/Structs.cs.meta
  79. 32
      Assets/Fullscreen/Editor/Windows/User32.cs
  80. 11
      Assets/Fullscreen/Editor/Windows/User32.cs.meta
  81. 15
      Assets/Fullscreen/FullscreenEditor.asmdef
  82. 7
      Assets/Fullscreen/FullscreenEditor.asmdef.meta
  83. BIN
      Assets/Fullscreen/Readme.pdf
  84. 9
      Assets/Fullscreen/Readme.pdf.meta
  85. 129
      Assets/HDRP/Act-4/Act4.asset

38
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: []

9
Assets/Fullscreen.meta

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: e58df2ef30bd5d94c9987dfbf4bd8838
folderAsset: yes
timeCreated: 1497142680
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

9
Assets/Fullscreen/Editor.meta

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: e88d8747d7da76847b620be9aa548d88
folderAsset: yes
timeCreated: 1471207248
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

52
Assets/Fullscreen/Editor/After.cs

@ -0,0 +1,52 @@
using System;
using System.Diagnostics;
using UnityEditor;
using UnityEngine;
namespace FullscreenEditor {
/// <summary>Utility class for running async tasks within the main thread.</summary>
public static class After {
/// <summary>Wait for a condition to become true, then executes the callback.</summary>
/// <param name="condition">Function that will be called every frame that returns whether to invoke the callback or not.</param>
/// <param name="callback">The callback to be called when the condition becomes true.</param>
/// <param name="timeoutMs">Maximum time to wait in milliseconds before cancelling the callback.</param>
public static void Condition(Func<bool> 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;
}
/// <summary>Wait for the given amount of editor frames, then executes the callback.</summary>
/// <param name="frames">The number of frames to wait for.</param>
/// <param name="callback">The callback to be called after the specified frames.</param>
public static void Frames(int frames, Action callback) {
var f = 0;
Condition(() => f++ >= frames, callback);
}
/// <summary>Wait for the given time, then executes the callback.</summary>
/// <param name="milliseconds">How long to wait until calling the callback, in milliseconds.</param>
/// <param name="callback">The callback to be called after the specified time.</param>
public static void Milliseconds(double milliseconds, Action callback) {
var end = EditorApplication.timeSinceStartup + (milliseconds / 1000f);
Condition(() => EditorApplication.timeSinceStartup >= end, callback);
}
}
}

11
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:

90
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));
}
}
}

11
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:

151
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;
}
}
}

11
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:

8
Assets/Fullscreen/Editor/Displays.meta

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 185b2b5b57344834298a7bbe0defe069
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

184
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<DisplayInfo> GetDisplays() {
var list = new List<DisplayInfo>();
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> 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.");
}
}
}
}

11
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:

109
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();
}
}
}

11
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:

26
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();
}
});
}
}
}

11
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:

166
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 {
/// <summary>Main entry point for finding, creating and closing <see cref="FullscreenContainer"/>.</summary>
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);
}
/// <summary>Return all <see cref="FullscreenContainer"/> instances.</summary>
/// <param name="cached">Allow returning cached content.</param>
/// <param name="ignoreUnknownState">Do not return fullscreen containers that don't have a valid ContainerWindow.</param>
public static FullscreenContainer[] GetAllFullscreen(bool cached = true, bool ignoreUnknownState = true) {
if(cached && cachedFullscreen != null && cachedFullscreenAll != null)
return ignoreUnknownState ?
cachedFullscreen :
cachedFullscreenAll;
cachedFullscreenAll = Resources.FindObjectsOfTypeAll<FullscreenContainer>();
if(!ignoreUnknownState)
return cachedFullscreenAll;
cachedFullscreen = cachedFullscreenAll
.Where(fs => fs.m_dst.Container != null)
.ToArray();
return cachedFullscreen;
}
/// <summary>Get the <see cref="FullscreenContainer"/> on the given point, or null if there is none.</summary>
public static FullscreenContainer GetFullscreenOnPoint(Vector2 point) {
return GetAllFullscreen()
.FirstOrDefault(fullscreen => fullscreen.Rect.Contains(point));
}
/// <summary>Get the <see cref="FullscreenContainer"/> that overlaps the given rect, or null if there is none.</summary>
public static FullscreenContainer GetFullscreenOnRect(Rect rect) {
return GetAllFullscreen()
.FirstOrDefault(fullscreen => fullscreen.Rect.Overlaps(rect));
}
/// <summary>Returns the parent <see cref="FullscreenContainer"/> for a given view or window, or null if it's not in fullscreen.</summary>
/// <param name="rootView">Compare by the root view, otherwise compare by the container.</param>
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
);
}
/// <summary>Create a <see cref="FullscreenWindow"/> for a given window.</summary>
/// <param name="window">The window that will go fullscreen. If null a new one will be instantiated based on the given type.</param>
/// <typeparam name="T">The type of the window to instantiate if the given window is null.</typeparam>
/// <returns>Returns the newly created <see cref="FullscreenWindow"/>.</returns>
public static FullscreenWindow MakeFullscreen<T>(T window = null) where T : EditorWindow {
return MakeFullscreen(typeof(T), window);
}
/// <summary>Create a <see cref="FullscreenWindow"/> for a given window.</summary>
/// <param name="type">The type of the window to instantiate if the given window is null.</param>
/// <param name="window">The window that will go fullscreen. If null a new one will be instantiated based on the given type.</param>
/// <param name="disposableWindow">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.</param>
/// <returns>Returns the newly created <see cref="FullscreenWindow"/>.</returns>
public static FullscreenWindow MakeFullscreen(Type type, EditorWindow window = null, bool disposableWindow = false) {
var rect = FullscreenRects.GetFullscreenRect(FullscreenPreferences.RectSource, window);
var fullscreen = ScriptableObject.CreateInstance<FullscreenWindow>();
fullscreen.OpenWindow(rect, type, window, disposableWindow);
return fullscreen;
}
/// <summary>Create a <see cref="FullscreenView"/> for a given view.</summary>
/// <param name="view">The view that will go fullscreen, cannot be null.</param>
/// <returns>Returns the newly created <see cref="FullscreenView"/>.</returns>
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<FullscreenView>();
fullscreen.OpenView(rect, view);
return fullscreen;
}
/// <summary>Open a new fullscreen if there's none open, otherwise, close the one already open.</summary>
/// <param name="window">The window that will go fullscreen. If null a new one will be instantiated based on the given type.</param>
/// <typeparam name="T">The type of the window to instantiate if the given window is null.</typeparam>
public static void ToggleFullscreen<T>(T window = null) where T : EditorWindow {
ToggleFullscreen(typeof(T), window);
}
/// <summary>Open a new fullscreen if there's none open, otherwise, close the one already open.</summary>
/// <param name="window">The window that will go fullscreen. If null a new one will be instantiated based on the given type.</param>
/// <param name="type">The type of the window to instantiate if the given window is null.</param>
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();
};
}
/// <summary>Open a new fullscreen if there's none open, otherwise, close the one already open.</summary>
/// <param name="view">The view that will go fullscreen, cannot be null.</param>
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();
};
}
}
}

13
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:

32
Assets/Fullscreen/Editor/FullscreenCallbacks.cs

@ -0,0 +1,32 @@
using System;
namespace FullscreenEditor {
/// <summary>
/// Utility callbacks for fullscreen state changes.
/// </summary>
public static class FullscreenCallbacks {
/// <summary>
/// Callback called before the views are restored to their original position.
/// </summary>
public static Action<FullscreenContainer> beforeFullscreenClose = (f) => { };
/// <summary>
/// Callback called before the container for the fullscreen view is created and
/// the views are moved between ContainerWindows.
/// </summary>
public static Action<FullscreenContainer> beforeFullscreenOpen = (f) => { };
/// <summary>
/// Callback called in the OnDestroy method of the FullscreenContainer, after the
/// views have been reverted to their orignal positions.
/// </summary>
public static Action<FullscreenContainer> afterFullscreenClose = (f) => { };
/// <summary>
/// Callback called after the fullscreen is opened and everything is already set up.
/// </summary>
public static Action<FullscreenContainer> afterFullscreenOpen = (f) => { };
}
}

11
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:

135
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 {
/// <summary>Manages the WindowContainers, Views and Windows that will be fullscreened.</summary>
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); }
}
/// <summary>The true view pyramid of this fullscreen container.</summary>
public ViewPyramid ActualViewPyramid {
get { return new ViewPyramid(m_dst.Container); }
}
/// <summary>The view that is currently fullscreened.</summary>
public View FullscreenedView {
get { return ActualViewPyramid.View; }
}
/// <summary>Position and size of the WindowContainer created for this fullscreen.</summary>
public Rect Rect {
get {
return m_dst.Container ?
m_dst.Container.GetPropertyValue<Rect>("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);
}
/// <summary>Destroy this container and exit fullscreen.</summary>
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);
}
/// <summary>Focus the view of this fullscreen.</summary>
public virtual void Focus() {
if (FullscreenedView && FullscreenedView.IsOfType(Types.GUIView))
FullscreenUtility.FocusView(FullscreenedView);
}
/// <summary>Gets wheter the view of this fullscreen is focused or not.</summary>
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
}
}

11
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:

114
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 {
/// <summary>Manages the WindowContainers, Views and Windows that will be fullscreened.</summary>
public abstract partial class FullscreenContainer : ScriptableObject {
/// <summary>The m_src will have all it's fields null if we've instantiated the window by ourselves.
/// <para>Window - The window to fullscreen, null if we're opening a view.</para>
/// <para>View - The view to fullscreen, or window.m_Parent if we're opening a window.</para>
/// <para>Container - The source container, from the source view.</para>
/// </summary>
[SerializeField] public ViewPyramid m_src;
/// <summary>
/// <para>Window - The placeholder window or a window created specially for this fullscreen.</para>
/// <para>View - HostView created for this fullscreen.</para>
/// <para>Container - The container that will be used for fullscreen, with its mode set to "Popup".</para>
/// </summary>
[SerializeField] public ViewPyramid m_dst;
/// <summary>Create a ContainerWindow and HostView, then asigns a window to them and shows it as a popup.</summary>
/// <param name="rect">The initial position of the ContainerWindow, can be changed later using the <see cref="Rect"/> property.</param>
/// <param name="childWindow">The initial window for the newly created ContainerWindow and HostView.</param>
/// <returns>Returns the pyramid of views we've created.</returns>
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 };
}
/// <summary>Prevents any repaint on the container window. This fixes some glitches on macOS.</summary>
/// <param name="containerWindow">The ContainerWindow to freeze the repaints.</param>
/// <param name="freeze">Wheter to freeze or unfreeze the container.</param>
protected void SetFreezeContainer(ContainerWindow containerWindow, bool freeze) {
containerWindow.InvokeMethod("SetFreezeDisplay", freeze);
}
/// <summary>Method that will be called just before creating the ContainerWindow for this fullscreen.</summary>
protected virtual void BeforeOpening() {
FullscreenCallbacks.beforeFullscreenOpen(this);
if (m_dst.Container)
new Exception("Container already has a fullscreened view");
}
/// <summary>Method that will be called after the creation of the ContainerWindow for this fullscreen.</summary>
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);
}
}
}

11
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:

121
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 {
/// <summary>Toggle fullscreen upon playmode change if <see cref="FullscreenPreferences.FullscreenOnPlayEnabled"/> is set to true.</summary>
[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<int>("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);
}
}
}

13
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:

409
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 {
/// <summary>Define a source mode to get a fullscreen rect.</summary>
public enum RectSourceMode {
/// <summary>The bounds of the main display.</summary>
MainDisplay,
/// <summary>Open on the display that the target window is located.</summary>
WindowDisplay,
/// <summary>The bounds of the display where the mouse pointer is.</summary>
AtMousePosition,
/// <summary>A rect that spans across all the displays. (Windows only)</summary>
Span,
/// <summary>A custom rect defined by <see cref="FullscreenPreferences.CustomRect"/>.</summary>
Custom,
Display1 = 0x100,
Display2 = 0x101,
Display3 = 0x102,
Display4 = 0x103,
Display5 = 0x104,
Display6 = 0x105,
Display7 = 0x106,
Display8 = 0x107
}
/// <summary>Contains preferences for the Fullscreen Editor plugin.</summary>
[InitializeOnLoad]
public static class FullscreenPreferences {
private const float LABEL_WIDTH = 300f;
private const string DEVELOPER_EMAIL = "[email protected]";
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/";
/// <summary>Current version of the Fullscreen Editor plugin.</summary>
public static readonly Version pluginVersion = new Version(2, 2, 8);
/// <summary>Release date of this version.</summary>
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<GUIContent> contents = new List<GUIContent>();
private static readonly PrefItem<Vector2> scroll = new PrefItem<Vector2>("Scroll", Vector2.zero, string.Empty, string.Empty);
/// <summary>Is the window toolbar currently visible?</summary>
public static readonly PrefItem<bool> ToolbarVisible;
/// <summary>Is Fullscreen on Play currently enabled?</summary>
public static readonly PrefItem<bool> FullscreenOnPlayEnabled;
/// <summary>Defines a source to get a fullscreen rect.</summary>
public static readonly PrefItem<RectSourceMode> RectSource;
/// <summary>Custom rect to be used when <see cref="RectSource"/> is set to <see cref="RectSourceMode.Custom"/>.</summary>
public static readonly PrefItem<Rect> CustomRect;
/// <summary>Disable notifications when opening fullscreen windows.</summary>
public static readonly PrefItem<bool> DisableNotifications;
/// <summary>Keep fullscreen views below other modal and utility windows.</summary>
public static readonly PrefItem<bool> KeepFullscreenBelow;
/// <summary>Disable background SceneView rendering when there are open fullscreen views to increase performance.</summary>
public static readonly PrefItem<bool> DisableSceneViewRendering;
/// <summary>Hide toolbars of all windows, not only the fullscreened (issue #80).</summary>
public static readonly PrefItem<bool> UseGlobalToolbarHiding;
/// <summary>Defines which display renders on each screen when using Mosaic.</summary>
public static readonly PrefItem<int[]> MosaicMapping;
/// <summary>Do not attempt to use wmctrl's fullscreen on Linux environments.</summary>
public static readonly PrefItem<bool> DoNotUseWmctrl;
/// <summary>Restore cursor lock and hide state after going in and out of fullscreen.</summary>
public static readonly PrefItem<bool> 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<bool>("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<bool>("FullscreenOnPlay", false, "Fullscreen On Play", "Override the \"Maximize on Play\" option of the game view to \"Fullscreen on Play\"");
RectSource = new PrefItem<RectSourceMode>("RectSource", RectSourceMode.MainDisplay, "Placement source", rectSourceTooltip);
CustomRect = new PrefItem<Rect>("CustomRect", FullscreenRects.GetMainDisplayRect(), "Custom Rect", string.Empty);
DisableNotifications = new PrefItem<bool>("DisableNotifications", false, "Disable Notifications", "Disable the notifications that shows up when opening a new fullscreen view.");
KeepFullscreenBelow = new PrefItem<bool>("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<bool>("DisableSceneViewRendering", true, "Disable Scene View Rendering", "Increase Fullscreen Editor performance by not rendering SceneViews while there are open fullscreen views.");
UseGlobalToolbarHiding = new PrefItem<bool>("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<int[]>("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<bool>("DoNotUseWmctrl", false, "Do not use wmctrl", "Avoid using 'wmctrl' helper when opening fullscreen windows");
RestoreCursorLockAndHideState = new PrefItem<bool>("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<GUIContent, bool> linkLabel = (label) =>
typeof(EditorGUILayout).HasMethod("LinkLabel", new[] { typeof(string), typeof(GUILayoutOption[]) }) ? // Issue #100
typeof(EditorGUILayout).InvokeMethod<bool>("LinkLabel", label, new GUILayoutOption[0]) : // < 2020.1
typeof(EditorGUILayout).InvokeMethod<bool>("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<string, string> EscapeURL = url => UnityEngine.Networking.UnityWebRequest.EscapeURL(url).Replace("+", "%20");
#else
Func<string, string> 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;
}
}
}
}

13
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:

193
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 {
/// <summary>Helper for getting fullscreen rectangles.</summary>
public static class FullscreenRects {
/// <summary>Represents a callback for user defined fullscreen rect calculation.</summary>
/// <param name="mode">The mode set in <see cref="FullscreenPreferences.RectSource"/></param>
/// <param name="rect">A rect calculated based on custom logic.</param>
/// <returns>Whether the rect calculated should be used or not.</returns>
public delegate bool FullscreenRectCallback(RectSourceMode mode, out Rect rect);
/// <summary>The number of monitors attached to this machine, returns -1 if the platform is not supported.</summary>
public static int ScreenCount {
get {
if (!FullscreenUtility.IsWindows)
return -1;
const int SM_CMONITORS = 80;
return User32.GetSystemMetrics(SM_CMONITORS);
}
}
/// <summary>Custom callback to allow the user to specify their own logic to how fullscreens will be arranged.
/// Check the documentation for usage examples.</summary>
public static FullscreenRectCallback CustomRectCallback { get; set; }
/// <summary>Returns a fullscreen rect</summary>
/// <param name="mode">The mode that will be used to retrieve the rect.</param>
/// <param name="targetWindow">The window that will be set fullscreen.</param>
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<Rect>("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);
}
}
/// <summary>Returns a rect with the dimensions of the main screen.
/// (Note that the position may not be right for multiple screen setups)</summary>
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);
}
/// <summary>Returns the rect of a given display index.</summary>
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;
}
/// <summary>Returns a rect defined by the user in the preferences.</summary>
public static Rect GetCustomUserRect() {
return FullscreenPreferences.CustomRect;
}
/// <summary>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.</summary>
/// <param name="mouseScreen">Should we get the rect on the screen where the mouse pointer is?</param>
public static Rect GetWorkAreaRect(bool mouseScreen) {
return Types.ContainerWindow.InvokeMethod<Rect>("FitRectToScreen", new Rect(Vector2.zero, Vector2.one * 10000f), true, mouseScreen);
}
/// <summary>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.</summary>
/// <param name="container">The ContainerWindow that will be used as reference for calulating border error.</param>
/// <param name="mouseScreen">Should we get the rect on the screen where the mouse pointer is?</param>
public static Rect GetWorkAreaRect(Object container, bool mouseScreen) {
return container.InvokeMethod<Rect>("FitWindowRectToScreen", new Rect(Vector2.zero, Vector2.one * 10000f), true, mouseScreen);
}
/// <summary>Returns the bounds rect of the screen that contains the given point. (Windows only)</summary>
/// <param name="point">The point relative to <see cref="RectSourceMode.Span"/></param>
public static Rect GetDisplayBoundsAtPoint(Vector2 point) {
return InternalEditorUtility.GetBoundsOfDesktopAtPoint(point);
}
/// <summary>Full virtual screen bounds, spanning across all monitors. (Windows only)</summary>
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);
}
}
}

13
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:

432
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 {
/// <summary>Clone of the internal UnityEditor.ShowMode.</summary>
public enum ShowMode {
/// <summary>Show as a normal window with max, min & close buttons.</summary>
NormalWindow = 0,
/// <summary>Used for a popup menu. On mac this means light shadow and no titlebar.</summary>
PopupMenu = 1,
/// <summary>Utility window - floats above the app. Disappears when app loses focus.</summary>
Utility = 2,
/// <summary>Window has no shadow or decorations. Used internally for dragging stuff around.</summary>
NoShadow = 3,
/// <summary>The Unity main window. On mac, this is the same as NormalWindow, except window doesn't have a close button.</summary>
MainWindow = 4,
/// <summary>Aux windows. The ones that close the moment you move the mouse out of them.</summary>
AuxWindow = 5,
/// <summary>Like PopupMenu, but without keyboard focus.</summary>
Tooltip = 6,
// Show as fullscreen window
Fullscreen = 8
}
/// <summary>Helper class for suppressing unity logs when calling a method that may show unwanted logs.</summary>
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;
}
}
/// <summary>Miscellaneous utilities for Fullscreen Editor.</summary>
[InitializeOnLoad]
public static class FullscreenUtility {
/// <summary>Contains a Texture2D icon loaded from a base64.</summary>
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);
}
/// <summary>Returns wheter the extension is running with debugging enabled.</summary>
public static bool DebugModeEnabled {
get {
#if FULLSCREEN_DEBUG
return true;
#else
return false;
#endif
}
}
/// <summary>The mouse position, can be called outside an OnGUI method.</summary>
public static Vector2 MousePosition { get { return mousePosition; } }
/// <summary>The icon of this plugin.</summary>
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=="
);
/// <summary>A smaller icon of this plugin.</summary>
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=="
);
/// <summary>The icon to show on the game view toolbar when fullscreen on play is enabled.</summary>
public static readonly Icon FullscreenOnPlayIcon = new Icon(
"iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAMAAAAMs7fIAAAAZlBMVEUAAAAZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRnaqT0eAAAAIXRSTlMA9rSVTunBWTwgFNzOrvvYo4x1ZGA4MALTx6eae3dTCgkQ40nLAAAAcklEQVQY053OORaFIBBE0WpsBQSchz+qvf9NSmCHJlZ4g3cKj8ddbD3q6pWmSwqRnjH/SIxKQ1WN4yOkMpSuDKuhLX41ZOEt3IJxxe0mLA7Ww0LLb87NkGvDJYYk7fkPNYWK0H8G9yIqY2rzHx9ix3i6E/A3BM5M0CXHAAAAAElFTkSuQmCC",
"iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAMAAAAMs7fIAAAAY1BMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+aRQ2gAAAAIHRSTlMA9rSV6cFZPCAU3M6uT/vYo4x1ZGBMODAJ08enmnt3U4mFYqQAAABvSURBVBjTnc43AoAwDATBkwHbOJBz1P9fiQvUUqByitXh98XG1xZlsYbhlYy5HTGdxEqkoqLEsjOJdLnJHRTd/pCQhtUwM/qPZwNmA22hIeUtpqZLte4VRRyWtIeqTITpmjC2zCJ9qNMe63wT8fce2Z8EsL04YKYAAAAASUVORK5CYII="
);
/// <summary>Show the notification of a newly opened <see cref="FullscreenView"/> about using the shortcut to close.</summary>
/// <param name="window">The fullscreen window.</param>
/// <param name="menuItemPath">The <see cref="MenuItem"/> path containing a shortcut.</param>
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);
}
/// <summary>Show a fullscreen notification in an <see cref="EditorWindow"/>.</summary>
/// <param name="window">The host of the notification.</param>
/// <param name="message">The message to show.</param>
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();
}
/// <summary>Does the given <see cref="MenuItem"/> path contains a key binding?</summary>
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);
}
/// <summary>Gets a human-readable shortcut.</summary>
/// <param name="menuItemPath">The <see cref="MenuItem"/> path containing a shortcut.</param>
/// <returns></returns>
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<Texture2D>(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;
}
}
/// <summary>Find an object by it's name and type.</summary>
/// <param name="name">The name of the object to search for.</param>
/// <typeparam name="T">The type of the object to search for.</typeparam>
public static T GetRef<T>(string name) where T : UnityObject {
return Resources.FindObjectsOfTypeAll<T>().FirstOrDefault(obj => obj.name == name);
}
/// <summary>Find an object by it's name and type.</summary>
/// <param name="name">The name of the object to search for.</param>
/// <param name="type">The type of the object to search for.</param>
public static UnityObject GetRef(Type type, string name) {
return Resources.FindObjectsOfTypeAll(type).FirstOrDefault(obj => obj.name == name);
}
/// <summary>Get the main view.</summary>
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");
}
/// <summary>Get the main game view.</summary>
public static EditorWindow GetMainGameView() {
if(Types.GameView.HasMethod("GetMainGameView")) { // Removed in 2019.3 alpha
return Types.GameView.InvokeMethod<EditorWindow>("GetMainGameView");
} else if(Types.PreviewEditorWindow.HasMethod("GetMainPreviewWindow")) { // Removed in 2019.3 beta
return Types.PreviewEditorWindow.InvokeMethod<EditorWindow>("GetMainPreviewWindow");
} else { // if (Types.PlayModeView.HasMethod("GetMainPlayModeView"))
return Types.PlayModeView.InvokeMethod<EditorWindow>("GetMainPlayModeView");
}
}
/// <summary>Get all the game views. This returns even the docked game views which are not visible.</summary>
public static EditorWindow[] GetGameViews() {
return Resources
.FindObjectsOfTypeAll(Types.GameView)
.Cast<EditorWindow>()
.ToArray();
}
/// <summary>Returns the focused view if it is a dock area with more than one tab, otherwise, returns the focused window.</summary>
public static ScriptableObject GetFocusedViewOrWindow() {
var mostSpecificView = Types.GUIView.GetPropertyValue<ScriptableObject>("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<Array>("allChildren").Length == viewHierarchy.Length;
if(alone && EditorWindow.focusedWindow.InvokeMethod<int>("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;
}
/// <summary>Returns all the parents of a given view.</summary>
public static ScriptableObject[] GetViewHierarchy(ScriptableObject view) {
if(!view)
return new ScriptableObject[0];
view.EnsureOfType(Types.View);
var list = new List<ScriptableObject>() { view };
var parent = view.GetPropertyValue<ScriptableObject>("parent");
while(parent) { // Get the least specific view
view = parent;
list.Add(view);
parent = view.GetPropertyValue<ScriptableObject>("parent");
}
return list.ToArray();
}
/// <summary>Get all the children view of a given view.</summary>
public static ScriptableObject[] GetAllViewChildren(ScriptableObject view) {
if(!view)
return new ScriptableObject[0];
view.EnsureOfType(Types.View);
return view.GetPropertyValue<Array>("allChildren")
.Cast<ScriptableObject>()
.ToArray();
}
/// <summary>Returns wheter a given view is focused or not.</summary>
public static bool IsViewFocused(ScriptableObject view) {
if(!view)
return false;
view.EnsureOfType(Types.View);
var focused = Types.GUIView.GetPropertyValue<ScriptableObject>("focusedView");
var children = GetAllViewChildren(view);
return children.Contains(focused);
}
/// <summary>Focus a view.</summary>
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);
}
}
}
/// <summary> Returns the display scaling of the editor, e.g. 1.5 if 125%</summary>
public static float GetDisplayScaling() {
return EditorGUIUtility.pixelsPerPoint;
}
/// <summary> Returns a screen rect corrected to fit the editor scaling</summary>
public static Rect DpiCorrectedArea(Rect area) {
var scaling = GetDisplayScaling();
area.width /= scaling;
area.height /= scaling;
return area;
}
/// <summary>Get the default height of the editor toolbars.</summary>
public static float GetToolbarHeight() {
try {
if(typeof(EditorGUI).HasField("kWindowToolbarHeight")) {
var result = typeof(EditorGUI).GetFieldValue<object>("kWindowToolbarHeight");
if(result is int)
return (int)result;
else
return result.GetPropertyValue<float>("value");
} else
return 17f; // Default on < 2019.3 versions
} catch(Exception e) {
if(FullscreenUtility.DebugModeEnabled)
Debug.LogException(e);
return 17f;
}
}
/// <summary>Set the default height of the editor toolbars.</summary>
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<object>("kWindowToolbarHeight");
result.SetFieldValue("m_Value", value);
return true;
} else {
return false;
}
} catch(Exception e) {
if(FullscreenUtility.DebugModeEnabled)
Debug.LogException(e);
return false;
}
}
/// <summary>Set Game View target display.</summary>
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");
}
}
}
}

13
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:

63
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<ContainerWindow>("window");
var containerB = b.GetPropertyValue<ContainerWindow>("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<PlaceholderWindow>();
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();
}
}
}

11
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:

234
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<View>("m_Parent");
var parentB = b.GetFieldValue<View>("m_Parent");
var containerA = parentA.GetPropertyValue<ContainerWindow>("window");
var containerB = parentB.GetPropertyValue<ContainerWindow>("window");
var selectedPaneA = parentA.GetPropertyValue<EditorWindow>("actualView");
var selectedPaneB = parentB.GetPropertyValue<EditorWindow>("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<List<EditorWindow>>("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<T>(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<bool>("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<PlaceholderWindow>() :
(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<View>("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();
}
}
}

11
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:

18
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);
};
}
}
}

7
Assets/Fullscreen/Editor/GameViewLowResolutionAspectRatios.cs.meta

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5a92ff5ca1709dd43bd7675f90970e44
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

35
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<bool>("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()));
}
}
}
}

11
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:

41
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);
}
}
}

11
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:

81
Assets/Fullscreen/Editor/Integration.cs

@ -0,0 +1,81 @@
using System;
using System.Linq;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace FullscreenEditor {
/// <summary>Helper class for enabling/disabling compilation symbols.</summary>
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");
}
}
/// <summary>Toggle a given define symbol.</summary>
/// <param name="directive">The define symbol to toggle.</param>
public static void ToggleDirectiveDefined(string directive) {
var defined = IsDirectiveDefined(directive);
SetDirectiveDefined(directive, !defined);
}
/// <summary>Enable or disable a given define symbol.</summary>
/// <param name="directive">The define symbol to set.</param>
/// <param name="enabled">Wheter to enable or disable this directive.</param>
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");
}
/// <summary>Get wheter the given directive is enabled or not.</summary>
/// <param name="directive">The name of the define symbol to check.</param>
public static bool IsDirectiveDefined(string directive) {
return GetAllDefines().Any(d => d == directive);
}
}
}

11
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:

42
Assets/Fullscreen/Editor/InternalTypes.cs

@ -0,0 +1,42 @@
using System;
namespace FullscreenEditor {
/// <summary>Class containing types of UnityEditor internal classes.</summary>
public static class Types {
/// <summary>UnityEditor.HostView</summary>
public static readonly Type HostView = ReflectionUtility.FindClass("UnityEditor.HostView");
/// <summary>UnityEditor.ContainerWindow</summary>
public static readonly Type ContainerWindow = ReflectionUtility.FindClass("UnityEditor.ContainerWindow");
/// <summary>UnityEditor.View</summary>
public static readonly Type View = ReflectionUtility.FindClass("UnityEditor.View");
/// <summary>UnityEditor.GUIView</summary>
public static readonly Type GUIView = ReflectionUtility.FindClass("UnityEditor.GUIView");
/// <summary>UnityEditor.GameView</summary>
public static readonly Type GameView = ReflectionUtility.FindClass("UnityEditor.GameView");
/// <summary>UnityEditor.PreviewEditorWindow</summary>
public static readonly Type PreviewEditorWindow = ReflectionUtility.FindClass("UnityEditor.PreviewEditorWindow");
/// <summary>UnityEditor.PlayModeView</summary>
public static readonly Type PlayModeView = ReflectionUtility.FindClass("UnityEditor.PlayModeView");
/// <summary>UnityEditor.MainView</summary>
public static readonly Type MainView = ReflectionUtility.FindClass("UnityEditor.MainView");
/// <summary>UnityEditor.WindowLayout</summary>
public static readonly Type WindowLayout = ReflectionUtility.FindClass("UnityEditor.WindowLayout");
/// <summary>UnityEngine.EnumDataUtility</summary>
public static readonly Type EnumDataUtility = ReflectionUtility.FindClass("UnityEngine.EnumDataUtility");
/// <summary>UnityEditor.PlayModeView.EnterPlayModeBehavior</summary>
// Enum type
public static readonly Type EnterPlayModeBehavior = PlayModeView?.GetNestedType("EnterPlayModeBehavior");
}
}

11
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:

65
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<EditorApplication.CallbackFunction>("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<ScriptableObject> GetAllContainerWindowsOrdered() {
var ordered = Types.ContainerWindow
.GetPropertyValue<ScriptableObject[]>("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<int>("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);
}
}
}
}

11
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:

8
Assets/Fullscreen/Editor/Linux.meta

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f159b5c7b27f37c1cac894c9b84d8069
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

22
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);
};
}
}
}

11
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:

75
Assets/Fullscreen/Editor/Linux/wmctrl.cs

@ -0,0 +1,75 @@
using System;
using UnityEditor;
using UnityEngine;
namespace FullscreenEditor.Linux {
/// <summary>wmctrl is a tool to interact with an X Window manager available on Linux platforms.</summary>
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;
}
/// <summary>Enable or disable native fullscreen for a given window.</summary>
/// <param name="fullscreen">Should the window be fullscreen or not.</param>
/// <param name="window">The window to changed. If null the active window will be fullscreened.</param>
public static void SetNativeFullscreen(bool fullscreen, EditorWindow window) {
if (window)
window.Focus();
Run("-r ':ACTIVE:' -b {0},fullscreen", fullscreen ? "add" : "remove");
}
/// <summary>Enable or disable native fullscreen for a given view.</summary>
/// <param name="fullscreen">Should the view be fullscreen or not.</param>
/// <param name="view">The view to changed. If null the active view will be fullscreened.</param>
public static void SetNativeFullscreen(bool fullscreen, ScriptableObject view) {
if (view)
FullscreenUtility.FocusView(view);
Run("-r ':ACTIVE:' -b {0},fullscreen", fullscreen ? "add" : "remove");
}
/// <summary>Toggles native fullscreen for a given window.</summary>
/// <param name="window">The window to be toggled fullscreen.</param>
public static void ToggleNativeFullscreen(EditorWindow window) {
if (window)
window.Focus();
Run("-r ':ACTIVE:' -b toggle,fullscreen");
}
/// <summary>Toggles native fullscreen for a given view.</summary>
/// <param name="view">The view to be toggled fullscreen.</param>
public static void ToggleNativeFullscreen(ScriptableObject view) {
if (view)
FullscreenUtility.FocusView(view);
Run("-r ':ACTIVE:' -b toggle,fullscreen");
}
}
}

11
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:

56
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 {
/// <summary>Helper class for logging and debugging.</summary>
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);
}
}
}

11
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:

176
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>(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<FullscreenWindow>();
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<IList>("m_Sections").Cast<object>().ToList();
var index = sections.FindIndex(section => section.GetFieldValue<GUIContent>("content").text == "Fullscreen");
window.SetPropertyValue("selectedSectionIndex", index);
});
#endif
}
private static T FindCandidateForFullscreen<T>(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<EditorWindow>()
.FirstOrDefault(window => !Fullscreen.GetFullscreenFromView(window));
}
}
}

13
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:

137
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<T> : 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);
}
}
}
}

11
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:

174
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 {
/// <summary>The window that will be shown in the place of the original view when creating a fullscreen container.</summary>
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<FullscreenContainer>(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<T>(Func<T> obj) {
try {
var i = obj();
return i == null? "null": i.ToString();
} catch {
return "invalid";
}
}
}
}

11
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:

128
Assets/Fullscreen/Editor/PrefItem.cs

@ -0,0 +1,128 @@
using System;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace FullscreenEditor {
/// <summary>Helper class for saving preferences.</summary>
/// <typeparam name="T">The type you want to save, must be marked as <see cref="SerializableAttribute"/></typeparam>
[Serializable]
public sealed class PrefItem<T> {
[SerializeField]
private T savedValue;
/// <summary>The key for saving the value.</summary>
public string Key { get; private set; }
/// <summary>The default value to use when there's none saved.</summary>
public T DefaultValue { get; private set; }
/// <summary>A label and an explanation of what this item is for.</summary>
public GUIContent Content { get; private set; }
/// <summary>Callback called whenever the saved value changes.</summary>
public Action<T> OnValueSaved { get; set; }
/// <summary>The value saved by this instance.</summary>
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<T> pb) { return pb.Value; }
public static implicit operator GUIContent(PrefItem<T> pb) { return pb.Content; }
}
/// <summary>Helper class for drawing the <see cref="PrefItem{T}"/> in the <see cref="PreferencesWindow"/>.</summary>
public static class PrefItemGUI {
public static void DoGUI(this PrefItem<int> pref) {
pref.Value = EditorGUILayout.IntField(pref.Content, pref.Value);
}
public static void DoGUI(this PrefItem<float> pref) {
pref.Value = EditorGUILayout.FloatField(pref.Content, pref.Value);
}
public static void DoGUI(this PrefItem<int> pref, int min, int max) {
pref.Value = EditorGUILayout.IntSlider(pref.Content, pref.Value, min, max);
}
public static void DoGUI(this PrefItem<float> pref, float min, float max) {
pref.Value = EditorGUILayout.Slider(pref.Content, pref.Value, min, max);
}
public static void DoGUI(this PrefItem<bool> pref) {
pref.Value = EditorGUILayout.Toggle(pref.Content, pref.Value);
}
public static void DoGUI(this PrefItem<string> pref) {
pref.Value = EditorGUILayout.TextField(pref.Content, pref.Value);
}
public static void DoGUI(this PrefItem<Color> pref) {
pref.Value = EditorGUILayout.ColorField(pref.Content, pref.Value);
}
public static void DoGUI(this PrefItem<Rect> pref) {
pref.Value = EditorGUILayout.RectField(pref.Content, pref.Value);
}
public static void DoGUI(this PrefItem<RectSourceMode> pref) {
pref.Value = (RectSourceMode)EditorGUILayout.EnumPopup(pref.Content, pref.Value);
}
}
}

13
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:

197
Assets/Fullscreen/Editor/ReflectionUtility.cs

@ -0,0 +1,197 @@
using System;
using System.Linq;
using System.Reflection;
using UnityEditor;
namespace FullscreenEditor {
/// <summary>Class containing method extensions for getting private and internal members.</summary>
public static class ReflectionUtility {
private static Assembly[] cachedAssemblies;
public const BindingFlags FULL_BINDING = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
/// <summary>Find a type by its name.</summary>
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);
}
/// <summary>Find a field of a type by its name.</summary>
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;
}
/// <summary>Find a property of a type by its name.</summary>
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;
}
/// <summary>Find a method of a type by its name.</summary>
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;
}
/// <summary>Get the value of the static field.</summary>
public static T GetFieldValue<T>(this Type type, string fieldName) { return (T)type.FindField(fieldName).GetValue(null); }
/// <summary>Get the value of the instance field.</summary>
public static T GetFieldValue<T>(this object obj, string fieldName) { return (T)obj.GetType().FindField(fieldName).GetValue(obj); }
/// <summary>Set the value of the static field.</summary>
public static void SetFieldValue(this Type type, string fieldName, object value) { type.FindField(fieldName).SetValue(null, value); }
/// <summary>Set the value of the instance field.</summary>
public static void SetFieldValue(this object obj, string fieldName, object value) { obj.GetType().FindField(fieldName).SetValue(obj, value); }
/// <summary>Get the value of the static property.</summary>
public static T GetPropertyValue<T>(this Type type, string propertyName) { return (T)type.FindProperty(propertyName).GetValue(null, null); }
/// <summary>Get the value of the instance property.</summary>
public static T GetPropertyValue<T>(this object obj, string propertyName) { return (T)obj.GetType().FindProperty(propertyName).GetValue(obj, null); }
/// <summary>Set the value of the static property.</summary>
public static void SetPropertyValue(this Type type, string propertyName, object value) { type.FindProperty(propertyName).SetValue(null, value, null); }
/// <summary>Set the value of the instance property.</summary>
public static void SetPropertyValue(this object obj, string propertyName, object value) { obj.GetType().FindProperty(propertyName).SetValue(obj, value, null); }
/// <summary>Invoke a static method on the type and return the result.</summary>
public static T InvokeMethod<T>(this Type type, string methodName, params object[] args) { return (T)type.FindMethod(methodName, args.Select(a => a.GetType()).ToArray()).Invoke(null, args); }
/// <summary>Invoke a method on the object instance and return the result.</summary>
public static T InvokeMethod<T>(this object obj, string methodName, params object[] args) { return (T)obj.GetType().FindMethod(methodName, args.Select(a => a.GetType()).ToArray()).Invoke(obj, args); }
/// <summary>Invoke a static method on the type.</summary>
public static void InvokeMethod(this Type type, string methodName, params object[] args) { type.FindMethod(methodName, args.Select(a => a.GetType()).ToArray()).Invoke(null, args); }
/// <summary>Invoke a method on the object instance.</summary>
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); }
/// <summary>Returns wheter the given type is the same as another one.</summary>
/// <param name="toCheck">Type that will be checked.</param>
/// <param name="type">Type to check against.</param>
/// <param name="orInherited">Returns true if the checked type is inherited from the type argument.</param>
public static bool IsOfType(this Type toCheck, Type type, bool orInherited = true) {
return type == toCheck || (orInherited && type.IsAssignableFrom(toCheck));
}
/// <summary>Returns wheter the given instance is of a given type.</summary>
/// <param name="obj">The instance to check.</param>
/// <param name="type">Type to check against.</param>
/// <param name="orInherited">Returns true if the instance is inherited from the type argument.</param>
public static bool IsOfType<T>(this T obj, Type type, bool orInherited = true) {
return obj.GetType().IsOfType(type, orInherited);
}
/// <summary>Throws an exception if the instance is not of the given type.</summary>
/// <param name="obj">The instance to check.</param>
/// <param name="type">Type to check against.</param>
/// <param name="orInherited">Do not throw if the instance is inherited from the type argument.</param>
public static void EnsureOfType<T>(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": ""
)
);
}
/// <summary>Returns whether the type defines the static field.</summary>
public static bool HasField(this Type type, string fieldName) {
return type.FindField(fieldName, false) != null;
}
/// <summary>Returns whether the type defines the static property.</summary>
public static bool HasProperty(this Type type, string propertyName) {
return type.FindProperty(propertyName, false) != null;
}
/// <summary>Returns whether the type defines the static method.</summary>
public static bool HasMethod(this Type type, string methodName, Type[] args = null) {
return type.FindMethod(methodName, args, false) != null;
}
/// <summary>Returns whether the object type defines the instance field.</summary>
public static bool HasField(this object obj, string fieldName) {
return obj.GetType().HasField(fieldName);
}
/// <summary>Returns whether the object type defines the instance property.</summary>
public static bool HasProperty(this object obj, string propertyName) {
return obj.GetType().HasProperty(propertyName);
}
/// <summary>Returns whether the object type defines the instance method.</summary>
public static bool HasMethod(this object obj, string methodName, Type[] args = null) {
return obj.GetType().HasMethod(methodName, args);
}
}
}

13
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:

43
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<FullscreenContainer> 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<FullscreenContainer> storeCursorVisible = (fs) => {
cursorVisible = Cursor.visible;
};
FullscreenCallbacks.afterFullscreenOpen += magic;
FullscreenCallbacks.afterFullscreenClose += magic;
FullscreenCallbacks.beforeFullscreenOpen += storeCursorVisible;
FullscreenCallbacks.beforeFullscreenClose += storeCursorVisible;
}
}
}

11
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:

289
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<Shortcut> fieldsInfo = new List<Shortcut>();
/* 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
}
}

13
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:

98
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 {
/// <summary>Represents the pyramid containing all the elements that make up a window.</summary>
[Serializable]
public struct ViewPyramid {
/// <summary>The actual window, may be null if the pyramid was created from a view or container.</summary>
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;
}
}
/// <summary>View that controls how the window (and child view) are drawn.</summary>
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;
}
}
/// <summary>The native window.</summary>
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;
/// <summary>Create a new instance and automatically assigns the window, view and container.</summary>
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<View>("m_Parent");
m_container = m_view.GetPropertyValue<ContainerWindow>("window");
} else if (viewOrWindow.IsOfType(Types.View)) {
m_window = null;
m_view = viewOrWindow;
m_container = m_view.GetPropertyValue<ContainerWindow>("window");
} else if (viewOrWindow.IsOfType(Types.ContainerWindow)) {
m_window = null;
m_view = viewOrWindow.GetPropertyValue<ContainerWindow>("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<EditorWindow>("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;
}
}
}

11
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:

8
Assets/Fullscreen/Editor/Windows.meta

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ad11a5b026b2b8244849852d5718d291
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

17
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);
}
}

11
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:

18
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);
}
}

11
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:

127
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 {
/// <summary>The device is part of the desktop.</summary>
AttachedToDesktop = 0x1,
MultiDriver = 0x2,
/// <summary>The device is part of the desktop.</summary>
PrimaryDevice = 0x4,
/// <summary>Represents a pseudo device used to mirror application drawing for remoting or other purposes.</summary>
MirroringDriver = 0x8,
/// <summary>The device is VGA compatible.</summary>
VGACompatible = 0x10,
/// <summary>The device is removable; it cannot be the primary display.</summary>
Removable = 0x20,
/// <summary>The device has more display modes than its output devices support.</summary>
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;
}
}

11
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:

32
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);
}
}

11
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:

15
Assets/Fullscreen/FullscreenEditor.asmdef

@ -0,0 +1,15 @@
{
"name": "FullscreenEditor",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": []
}

7
Assets/Fullscreen/FullscreenEditor.asmdef.meta

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 838d3286f0973344ab6e99d3951012f7
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Fullscreen/Readme.pdf

Binary file not shown.

9
Assets/Fullscreen/Readme.pdf.meta

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 0c3e30c36dfe6484c88009ed994d43a6
timeCreated: 1510520595
licenseType: Store
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

129
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

Loading…
Cancel
Save