This commit is contained in:
Даниил Заикин
2026-06-17 20:44:53 +03:00
parent 9d153773c2
commit b133e7c656
1880 changed files with 244545 additions and 0 deletions
@@ -0,0 +1,488 @@
#if UNITY_EDITOR
using System.IO;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEngine.SceneManagement;
[InitializeOnLoad]
public static class SceneSwitcherToolbar
{
private const string NotInBuildSuffix = " (not in build)";
private const string FetchAllScenesKey = "SceneSwitcher_FetchAllScenes";
private static string[] sceneNames = new string[0];
private static int selectedIndex = 0;
private static string lastActiveScene = string.Empty;
private static VisualElement toolbarUI;
private static int lastToolbarInstanceId = -1;
private static double nextToolbarCheckTime = 0d;
private const double ToolbarCheckInterval = 0.25d;
private static bool fetchAllScenes
{
get => EditorPrefs.GetBool(FetchAllScenesKey, false);
set => EditorPrefs.SetBool(FetchAllScenesKey, value);
}
static SceneSwitcherToolbar()
{
RefreshSceneList();
SelectCurrentScene();
EditorSceneManager.activeSceneChangedInEditMode += (_, __) => UpdateSceneSelection();
EditorApplication.playModeStateChanged += OnPlayModeChanged;
EditorApplication.update += EnsureToolbarExists;
AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload;
EditorApplication.delayCall += AddToolbarUI;
}
private static void OnAfterAssemblyReload()
{
EditorApplication.delayCall += AddToolbarUI;
}
private static void EnsureToolbarExists()
{
if (EditorApplication.timeSinceStartup < nextToolbarCheckTime)
return;
nextToolbarCheckTime = EditorApplication.timeSinceStartup + ToolbarCheckInterval;
var toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar");
if (toolbarType == null)
return;
var toolbars = Resources.FindObjectsOfTypeAll(toolbarType);
if (toolbars == null || toolbars.Length == 0)
return;
foreach (var toolbar in toolbars)
{
TryInjectIntoToolbar(toolbarType, toolbar);
}
}
private static void TryInjectIntoToolbar(System.Type toolbarType, UnityEngine.Object toolbar)
{
var rootField = toolbarType.GetField("m_Root", BindingFlags.NonPublic | BindingFlags.Instance);
if (rootField == null)
return;
if (rootField.GetValue(toolbar) is not VisualElement root)
return;
var leftContainer = root.Q("ToolbarZoneLeftAlign");
if (leftContainer == null)
return;
var existing = leftContainer.Q<IMGUIContainer>("SceneSwitcherToolbar");
if (existing != null)
{
toolbarUI = existing;
return;
}
var newToolbarUI = new IMGUIContainer(DrawToolbarGUI);
newToolbarUI.name = "SceneSwitcherToolbar";
newToolbarUI.style.flexGrow = 0;
newToolbarUI.style.flexShrink = 1;
newToolbarUI.style.minWidth = 80;
newToolbarUI.style.maxWidth = 320;
newToolbarUI.style.marginLeft = 4;
newToolbarUI.style.marginRight = 4;
newToolbarUI.style.overflow = Overflow.Hidden;
leftContainer.Add(newToolbarUI);
toolbarUI = newToolbarUI;
}
private static void AddToolbarUI()
{
var toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar");
if (toolbarType == null)
return;
var toolbars = Resources.FindObjectsOfTypeAll(toolbarType);
if (toolbars == null || toolbars.Length == 0)
return;
foreach (var toolbar in toolbars)
{
TryInjectIntoToolbar(toolbarType, toolbar);
}
}
private static void DrawToolbarGUI()
{
CheckAndRefreshScenes();
GUILayout.BeginHorizontal(EditorStyles.toolbar);
if (sceneNames == null || sceneNames.Length == 0)
{
GUILayout.Label("No scenes", EditorStyles.toolbarButton, GUILayout.Width(100), GUILayout.Height(18));
GUILayout.EndHorizontal();
return;
}
if (selectedIndex < 0 || selectedIndex >= sceneNames.Length)
selectedIndex = 0;
bool isPlaying = EditorApplication.isPlaying;
bool currentSceneNotInBuild =
selectedIndex >= 0 &&
selectedIndex < sceneNames.Length &&
sceneNames[selectedIndex].EndsWith(NotInBuildSuffix);
float viewWidth = Mathf.Max(120f, EditorGUIUtility.currentViewWidth);
const float toggleWidth = 22f;
const float addWidth = 22f;
const float spacing = 0f;
const float controlHeight = 18f;
// Порог узкого режима
bool compactMode = viewWidth < 1400f;
float sceneButtonWidth = compactMode
? 22f
: Mathf.Clamp(
viewWidth - toggleWidth - addWidth - 10f,
90f,
190f
);
EditorGUI.BeginDisabledGroup(isPlaying);
bool newFetchAllScenes = GUILayout.Toggle(
fetchAllScenes,
new GUIContent("A", "Toggle All Scenes / Build Settings scenes"),
EditorStyles.toolbarButton,
GUILayout.Width(toggleWidth),
GUILayout.Height(controlHeight)
);
if (newFetchAllScenes != fetchAllScenes)
{
fetchAllScenes = newFetchAllScenes;
RefreshSceneList();
SelectCurrentScene();
GUIUtility.ExitGUI();
}
EditorGUI.EndDisabledGroup();
GUILayout.Space(spacing);
EditorGUI.BeginDisabledGroup(isPlaying || fetchAllScenes);
if (GUILayout.Button(
new GUIContent("+", "Add current scene to Build Settings"),
EditorStyles.toolbarButton,
GUILayout.Width(addWidth),
GUILayout.Height(controlHeight)))
{
AddCurrentSceneToBuildSettings();
GUIUtility.ExitGUI();
}
EditorGUI.EndDisabledGroup();
GUILayout.Space(spacing);
EditorGUI.BeginDisabledGroup(isPlaying);
Rect sceneButtonRect = GUILayoutUtility.GetRect(
GUIContent.none,
GetSceneButtonStyle(currentSceneNotInBuild),
GUILayout.Width(sceneButtonWidth),
GUILayout.Height(controlHeight)
);
string buttonText = compactMode ? "▼" : GetDisplaySceneName();
string tooltip = compactMode ? "Open scene list" : "Current scene";
if (GUI.Button(
sceneButtonRect,
new GUIContent(buttonText, tooltip),
compactMode ? EditorStyles.toolbarButton : GetSceneButtonStyle(currentSceneNotInBuild)))
{
ShowSceneMenu(sceneButtonRect);
}
EditorGUI.EndDisabledGroup();
GUILayout.EndHorizontal();
}
private static GUIStyle GetSceneButtonStyle(bool highlight)
{
var style = new GUIStyle(EditorStyles.toolbarDropDown)
{
alignment = TextAnchor.MiddleLeft,
fixedHeight = 20,
clipping = TextClipping.Clip,
imagePosition = ImagePosition.TextOnly,
padding = new RectOffset(6, 18, 0, 0),
margin = new RectOffset(0, 0, 0, 0),
fontStyle = highlight ? FontStyle.Italic : FontStyle.Normal
};
if (highlight)
style.normal.textColor = new Color(1f, 0.82f, 0.35f);
return style;
}
private static string GetDisplaySceneName()
{
if (sceneNames == null || sceneNames.Length == 0)
return "No scenes";
if (selectedIndex < 0 || selectedIndex >= sceneNames.Length)
return "No scene";
return sceneNames[selectedIndex];
}
private static void ShowSceneMenu(Rect buttonRect)
{
var menu = new GenericMenu();
string currentDisplayName = GetDisplaySceneName();
foreach (string displayName in sceneNames)
{
string cleanName = StripNotInBuildSuffix(displayName);
bool isCurrent = displayName == currentDisplayName;
menu.AddItem(new GUIContent(cleanName), isCurrent, () =>
{
OpenSceneByName(cleanName, additive: false);
});
}
buttonRect.y += buttonRect.height - 10f;
menu.DropDown(buttonRect);
}
private static void PingSceneAsset(string scenePath)
{
var asset = AssetDatabase.LoadAssetAtPath<SceneAsset>(scenePath);
if (asset != null)
{
EditorGUIUtility.PingObject(asset);
Selection.activeObject = asset;
}
}
private static string StripNotInBuildSuffix(string displaySceneName)
{
return displaySceneName.EndsWith(NotInBuildSuffix)
? displaySceneName.Substring(0, displaySceneName.Length - NotInBuildSuffix.Length)
: displaySceneName;
}
private static void OpenSceneByName(string sceneName, bool additive)
{
string scenePath = FindScenePathByName(sceneName, fetchAllScenes);
if (string.IsNullOrEmpty(scenePath))
{
Debug.LogError("Scene not found: " + sceneName);
return;
}
if (!additive)
{
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
return;
EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
}
else
{
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
return;
EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
}
}
private static string FindScenePathByName(string sceneName, bool searchAllAssets)
{
if (searchAllAssets)
{
return Directory.GetFiles("Assets", "*.unity", SearchOption.AllDirectories)
.FirstOrDefault(path => Path.GetFileNameWithoutExtension(path) == sceneName);
}
return EditorBuildSettings.scenes
.FirstOrDefault(scene =>
scene.enabled &&
Path.GetFileNameWithoutExtension(scene.path) == sceneName
)?.path;
}
private static void AddCurrentSceneToBuildSettings()
{
string currentScenePath = EditorSceneManager.GetActiveScene().path;
if (string.IsNullOrEmpty(currentScenePath))
{
EditorUtility.DisplayDialog(
"Scene Not Saved",
"Please save the scene before adding it to Build Settings.",
"OK"
);
return;
}
var buildScenes = EditorBuildSettings.scenes.ToList();
int existingIndex = buildScenes.FindIndex(s => s.path == currentScenePath);
if (existingIndex >= 0)
{
if (!buildScenes[existingIndex].enabled)
{
buildScenes[existingIndex] = new EditorBuildSettingsScene(currentScenePath, true);
EditorBuildSettings.scenes = buildScenes.ToArray();
Debug.Log($"<color=green>Scene '{Path.GetFileNameWithoutExtension(currentScenePath)}' enabled in Build Settings.</color>");
}
else
{
Debug.Log($"Scene '{Path.GetFileNameWithoutExtension(currentScenePath)}' is already in Build Settings.");
}
}
else
{
buildScenes.Add(new EditorBuildSettingsScene(currentScenePath, true));
EditorBuildSettings.scenes = buildScenes.ToArray();
Debug.Log($"<color=green>Scene '{Path.GetFileNameWithoutExtension(currentScenePath)}' added to Build Settings.</color>");
}
RefreshSceneList();
SelectCurrentScene();
}
private static void RefreshSceneList()
{
if (fetchAllScenes)
{
var buildSceneSet = new HashSet<string>(
EditorBuildSettings.scenes
.Where(scene => scene.enabled)
.Select(scene => Path.GetFileNameWithoutExtension(scene.path))
);
sceneNames = Directory.GetFiles("Assets", "*.unity", SearchOption.AllDirectories)
.Select(Path.GetFileNameWithoutExtension)
.Where(n => !string.IsNullOrWhiteSpace(n))
.Distinct()
.OrderBy(n => n)
.Select(name => buildSceneSet.Contains(name) ? name : name + NotInBuildSuffix)
.ToArray();
}
else
{
sceneNames = EditorBuildSettings.scenes
.Where(scene => scene.enabled)
.Select(scene => Path.GetFileNameWithoutExtension(scene.path))
.Where(n => !string.IsNullOrWhiteSpace(n))
.Distinct()
.OrderBy(n => n)
.ToArray();
}
}
private static void CheckAndRefreshScenes()
{
string[] currentScenes;
if (fetchAllScenes)
{
var buildSceneSet = new HashSet<string>(
EditorBuildSettings.scenes
.Where(scene => scene.enabled)
.Select(scene => Path.GetFileNameWithoutExtension(scene.path))
);
currentScenes = Directory.GetFiles("Assets", "*.unity", SearchOption.AllDirectories)
.Select(Path.GetFileNameWithoutExtension)
.Where(n => !string.IsNullOrWhiteSpace(n))
.Distinct()
.OrderBy(n => n)
.Select(name => buildSceneSet.Contains(name) ? name : name + NotInBuildSuffix)
.ToArray();
}
else
{
currentScenes = EditorBuildSettings.scenes
.Where(scene => scene.enabled)
.Select(scene => Path.GetFileNameWithoutExtension(scene.path))
.Where(n => !string.IsNullOrWhiteSpace(n))
.Distinct()
.OrderBy(n => n)
.ToArray();
}
if (!currentScenes.SequenceEqual(sceneNames))
{
sceneNames = currentScenes;
SelectCurrentScene();
}
}
private static void SelectCurrentScene()
{
string currentScene = Path.GetFileNameWithoutExtension(EditorSceneManager.GetActiveScene().path);
if (string.IsNullOrEmpty(currentScene))
{
selectedIndex = 0;
lastActiveScene = string.Empty;
return;
}
int index = System.Array.FindIndex(sceneNames, n => StripNotInBuildSuffix(n) == currentScene);
if (index >= 0)
{
selectedIndex = index;
}
else
{
string displayName = currentScene + NotInBuildSuffix;
sceneNames = new[] { displayName }.Concat(sceneNames).ToArray();
selectedIndex = 0;
}
lastActiveScene = currentScene;
}
private static void UpdateSceneSelection()
{
string currentScene = Path.GetFileNameWithoutExtension(EditorSceneManager.GetActiveScene().path);
if (currentScene != lastActiveScene)
{
SelectCurrentScene();
}
}
private static void OnPlayModeChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.EnteredPlayMode ||
state == PlayModeStateChange.ExitingPlayMode ||
state == PlayModeStateChange.EnteredEditMode)
{
EditorApplication.delayCall += AddToolbarUI;
}
}
}
#endif