using UnityEditor; using UnityEngine; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Linq; public class MoveToWindow : EditorWindow { // ------------------------------------------------------------------------ // Core Variables // ------------------------------------------------------------------------ private static List selectedObjects; private static List topLevelObjects; private static HierarchyDecoratorSettings settings; // ------------------------------------------------------------------------ // UI State Variables // ------------------------------------------------------------------------ private Vector2 scrollPosition; private string filterText = ""; private bool isPinned = false; private static Dictionary colorFilters = new Dictionary(); private bool showDefaultColor = true; // ------------------------------------------------------------------------ // Feature: Shortcut (Spacebar) - GLOBAL STATE // ------------------------------------------------------------------------ // Must be static to be accessed by SceneView delegate private static GameObject shortcutTarget = null; // ------------------------------------------------------------------------ // Feature: Create New Group // ------------------------------------------------------------------------ private bool showCreationSection = false; private string newGroupName = "New Group"; private int selectedColorIdForCreation = 1; private static bool resetPositionOnMove = false; // ------------------------------------------------------------------------ // Menu Items & Initialization // ------------------------------------------------------------------------ [MenuItem("Window/Move To...")] public static void ShowWindow() { LoadSettings(); UpdateTopLevelObjects(); MoveToWindow window = GetWindow("Move To..."); UpdateSelectedObjects(); window.Show(); } [MenuItem("GameObject/Move To...", false, -100)] private static void MoveToContextMenu(MenuCommand command) { LoadSettings(); UpdateSelectedObjects(); ShowWindow(); } private void OnEnable() { LoadSettings(); // Subscribe to Scene View updates to handle global shortcuts SceneView.duringSceneGui -= OnSceneGUI; // Safety unsubscribe SceneView.duringSceneGui += OnSceneGUI; } private void OnDisable() { // Unsubscribe to prevent errors when window is closed SceneView.duringSceneGui -= OnSceneGUI; } // ------------------------------------------------------------------------ // Global Shortcut Handling (Scene View) // ------------------------------------------------------------------------ private static void OnSceneGUI(SceneView sceneView) { Event e = Event.current; // Detect Spacebar press in Scene View if (e != null && e.type == EventType.KeyDown && e.keyCode == KeyCode.Space) { if (shortcutTarget != null) { // Refresh selection manually because OnGUI isn't running UpdateSelectedObjects(); if (selectedObjects != null && selectedObjects.Count > 0) { MoveObjectsToTab(selectedObjects, shortcutTarget); // Consume the event so Unity doesn't do default actions (like maximizing view) e.Use(); } } } } // ------------------------------------------------------------------------ // Main GUI Loop (Window View) // ------------------------------------------------------------------------ private void OnGUI() { // Handle shortcut inside the window as well HandleLocalKeyboardShortcut(); if (settings == null) LoadSettings(); if (topLevelObjects == null) UpdateTopLevelObjects(); EditorGUILayout.BeginHorizontal(); GUILayout.Space(10); EditorGUILayout.BeginVertical(); { GUILayout.Space(10); DrawHeader(); GUILayout.Space(10); DrawControls(); DrawCreationSection(); DrawHideColorsSection(); // Info Box about Shortcut (Active state) if (shortcutTarget != null) { GUI.backgroundColor = new Color(0.8f, 1f, 0.8f); EditorGUILayout.HelpBox($"ACTIVE: Press SPACE in Scene View to move objects to: {shortcutTarget.name}", MessageType.Info); GUI.backgroundColor = Color.white; } DrawTabsList(); DrawFooter(); GUILayout.Space(10); } EditorGUILayout.EndVertical(); GUILayout.Space(10); EditorGUILayout.EndHorizontal(); } // ------------------------------------------------------------------------ // Input Handling (Local Window) // ------------------------------------------------------------------------ private void HandleLocalKeyboardShortcut() { Event e = Event.current; if (e.type == EventType.KeyDown && e.keyCode == KeyCode.Space) { if (shortcutTarget != null) { UpdateSelectedObjects(); // Fix: Refresh selection before moving if (selectedObjects != null && selectedObjects.Count > 0) { MoveObjectsToTab(selectedObjects, shortcutTarget); e.Use(); Repaint(); } } } } // ------------------------------------------------------------------------ // Draw Methods // ------------------------------------------------------------------------ private void DrawHeader() { GUILayout.Label("Select a tab to move the selected objects:", new GUIStyle(EditorStyles.boldLabel) { alignment = TextAnchor.MiddleCenter }); EditorGUILayout.BeginHorizontal(); isPinned = EditorGUILayout.Toggle("Pin Window", isPinned); GUILayout.FlexibleSpace(); if (GUILayout.Button(EditorGUIUtility.IconContent("d_SettingsIcon"), GUILayout.Width(25), GUILayout.Height(19))) { Selection.activeObject = settings; } EditorGUILayout.EndHorizontal(); filterText = EditorGUILayout.TextField("Filter tabs", filterText); } private void DrawControls() { EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); if (GUILayout.Button("Enable All", GUILayout.Width(100))) SetAllTabsActiveState(true); if (GUILayout.Button("Disable All", GUILayout.Width(100))) SetAllTabsActiveState(false); if (GUILayout.Button("Collapse All", GUILayout.Width(100))) CollapseAllInHierarchy(); GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); GUILayout.Space(5); } private void DrawHideColorsSection() { GUILayout.Space(5); GUILayout.Label("----------------------------------------------------------", EditorStyles.centeredGreyMiniLabel); GUILayout.Label("Hide Colors", new GUIStyle(EditorStyles.boldLabel) { alignment = TextAnchor.MiddleCenter }); EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); var keys = colorFilters.Keys.ToList(); try { if (settings != null) { showDefaultColor = EditorGUILayout.ToggleLeft(new GUIContent(" ", MakeColorIcon(settings.defaultBackgroundColor)), showDefaultColor, GUILayout.Width(35)); } foreach (var colorEntry in keys) { colorFilters[colorEntry] = EditorGUILayout.ToggleLeft(new GUIContent(" ", MakeColorIcon(colorEntry)), colorFilters[colorEntry], GUILayout.Width(35)); } } finally { GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); } GUILayout.Label("----------------------------------------------------------", EditorStyles.centeredGreyMiniLabel); // --- NEW HELP TEXT (BLUE) --- GUIStyle helpStyle = new GUIStyle(EditorStyles.miniLabel); helpStyle.alignment = TextAnchor.MiddleCenter; helpStyle.normal.textColor = new Color(0.4f, 0.7f, 1f); // Nice Blue Color helpStyle.wordWrap = true; GUILayout.Label("Check the box to set the [SPACE] shortcut target. Press Space to move selected objects.", helpStyle); // ---------------------------- GUILayout.Space(10); } private void DrawCreationSection() { EditorGUILayout.BeginVertical(GUI.skin.box); showCreationSection = EditorGUILayout.Foldout(showCreationSection, "Create New Group", true); if (showCreationSection) { GUILayout.Space(5); EditorGUILayout.BeginHorizontal(); GUILayout.Label("Name:", GUILayout.Width(45)); newGroupName = EditorGUILayout.TextField(newGroupName); EditorGUILayout.EndHorizontal(); GUILayout.Space(5); GUILayout.Label("Pick Color:", EditorStyles.miniBoldLabel); DrawColorSelectionGrid(); GUILayout.Space(5); EditorGUILayout.BeginHorizontal(); resetPositionOnMove = EditorGUILayout.ToggleLeft("Reset Position (0,0,0)", resetPositionOnMove, GUILayout.Width(150)); GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); GUILayout.Space(5); Color originalBg = GUI.backgroundColor; GUI.backgroundColor = new Color(0.6f, 0.9f, 0.6f); if (GUILayout.Button("Create & Move Objects", GUILayout.Height(25))) { CreateAndMove(); } GUI.backgroundColor = originalBg; GUILayout.Space(5); } EditorGUILayout.EndVertical(); GUILayout.Space(10); } private void DrawColorSelectionGrid() { if (settings == null || settings.colorSchemes.Count == 0) return; int columns = 8; int buttonsInRow = 0; GUILayout.BeginHorizontal(); foreach (var scheme in settings.colorSchemes) { Rect rect = GUILayoutUtility.GetRect(30, 20); if (GUI.Button(rect, GUIContent.none, GUIStyle.none)) { selectedColorIdForCreation = scheme.identifier; } EditorGUI.DrawRect(rect, scheme.activeBackgroundColor); if (selectedColorIdForCreation == scheme.identifier) { DrawOutline(rect, 2, Color.white); } else { DrawOutline(rect, 1, new Color(0, 0, 0, 0.3f)); } buttonsInRow++; if (buttonsInRow >= columns) { GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); buttonsInRow = 0; } GUILayout.Space(4); } GUILayout.EndHorizontal(); } private void DrawTabsList() { scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); if (topLevelObjects != null) { foreach (var obj in topLevelObjects) { if (obj == null) continue; Color tabColor = CalculateColorForObject(obj); if (!showDefaultColor && settings != null && tabColor == settings.defaultBackgroundColor) continue; if (colorFilters.ContainsKey(tabColor) && colorFilters[tabColor] == false) continue; string cleanName = Regex.Replace(obj.name, @"^\d*=\s*|\s*=.*$", "").Trim(); if (obj.name.Contains("= Clean")) continue; if (!string.IsNullOrEmpty(filterText) && !cleanName.ToLower().Contains(filterText.ToLower())) continue; DrawTabItem(obj, cleanName, tabColor); } } EditorGUILayout.EndScrollView(); } private void DrawTabItem(GameObject obj, string cleanName, Color tabColor) { EditorGUILayout.BeginHorizontal(); // 1. Active Toggle bool isActive = obj.activeSelf; bool newActiveState = EditorGUILayout.Toggle(isActive, GUILayout.Width(20)); if (newActiveState != isActive) obj.SetActive(newActiveState); GUILayout.Space(5); // 2. Color Indicator GUIStyle colorStyle = new GUIStyle(GUI.skin.box) { normal = { background = MakeTex(1, 1, tabColor) }, margin = new RectOffset(0, 0, 4, 4), fixedWidth = 15, fixedHeight = 15 }; GUILayout.Box(GUIContent.none, colorStyle); GUILayout.Space(10); // 3. Main Button (Folder Name) // ZMIANA: Kolor tekstu zależny od aktywności obiektu GUIStyle tabButtonStyle = new GUIStyle(GUI.skin.button) { alignment = TextAnchor.MiddleCenter, normal = { background = MakeTex(1, 1, obj.activeSelf ? new Color(0.25f, 0.25f, 0.25f) : new Color(0.18f, 0.18f, 0.18f)), textColor = obj.activeSelf ? new Color(0.9f, 0.9f, 0.9f) : new Color(0.5f, 0.5f, 0.5f) }, hover = { background = MakeTex(1, 1, new Color(0.3f, 0.3f, 0.3f)), textColor = Color.white } }; if (GUILayout.Button(cleanName, tabButtonStyle, GUILayout.Height(22), GUILayout.MinWidth(200))) { UpdateSelectedObjects(); // Ensure fresh selection MoveObjectsToTab(selectedObjects, obj); if (!isPinned) Close(); } GUILayout.Space(10); // 4. Shortcut Toggle bool isShortcutTarget = (shortcutTarget == obj); bool newShortcutState = EditorGUILayout.Toggle(isShortcutTarget, GUILayout.Width(20)); if (newShortcutState != isShortcutTarget) { if (newShortcutState) shortcutTarget = obj; else shortcutTarget = null; } if (isShortcutTarget) { GUIStyle spaceLabelStyle = new GUIStyle(EditorStyles.miniLabel) { // ZMIANA TUTAJ: // Zamiast Color.green wpisz np. Color.cyan (jasny błękit) // lub własny kolor: new Color(0.4f, 0.7f, 1f) normal = { textColor = new Color(0.4f, 0.7f, 1f) }, fontStyle = FontStyle.Bold, alignment = TextAnchor.MiddleLeft }; GUILayout.Label("[SPACE]", spaceLabelStyle, GUILayout.Width(50)); } else { GUILayout.Space(54); } // 5. Show In Hierarchy Button GUIStyle showInHierarchyStyle = new GUIStyle(GUI.skin.button) { alignment = TextAnchor.MiddleCenter, normal = { textColor = new Color(0.8f, 0.8f, 0.8f) }, hover = { textColor = Color.white }, fontSize = 10, fontStyle = FontStyle.Italic }; if (GUILayout.Button("Show", showInHierarchyStyle, GUILayout.Width(45), GUILayout.Height(22))) { EditorGUIUtility.PingObject(obj); } EditorGUILayout.EndHorizontal(); GUILayout.Space(6); } private void DrawFooter() { GUILayout.Space(10); Color oldColor = GUI.backgroundColor; GUI.backgroundColor = new Color(0.8f, 0.7f, 0.7f); GUIStyle rootBtnStyle = new GUIStyle(GUI.skin.button); rootBtnStyle.fontStyle = FontStyle.Bold; rootBtnStyle.fontSize = 11; if (GUILayout.Button("Move to Root (Unparent)", rootBtnStyle, GUILayout.Height(25))) { MoveToRoot(); } GUI.backgroundColor = oldColor; GUILayout.Space(5); GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); string status = (selectedObjects != null && selectedObjects.Count > 0) ? $"{selectedObjects.Count} objects selected" : "No objects selected"; GUILayout.Label(status, EditorStyles.centeredGreyMiniLabel); GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); } // ------------------------------------------------------------------------ // Logic Methods // ------------------------------------------------------------------------ private void CreateAndMove() { if (string.IsNullOrEmpty(newGroupName)) return; string finalName = $"{selectedColorIdForCreation}={newGroupName.Trim()}"; GameObject header = new GameObject(finalName); Undo.RegisterCreatedObjectUndo(header, "Create Group"); if (Selection.activeGameObject != null && !selectedObjects.Contains(Selection.activeGameObject)) { GameObjectUtility.SetParentAndAlign(header, Selection.activeGameObject); } UpdateTopLevelObjects(); UpdateSelectedObjects(); MoveObjectsToTab(selectedObjects, header); newGroupName = "New Group"; GUI.FocusControl(null); Repaint(); } private void MoveToRoot() { UpdateSelectedObjects(); if (selectedObjects == null) return; foreach (var obj in selectedObjects) { if (obj != null) { Undo.SetTransformParent(obj.transform, null, "Move to Root"); if (resetPositionOnMove) { Undo.RecordObject(obj.transform, "Zero Position"); obj.transform.position = Vector3.zero; } } } } private static void MoveObjectsToTab(List objects, GameObject targetTab) { if (objects == null || targetTab == null) return; foreach (var obj in objects) { if (obj != null) { Undo.SetTransformParent(obj.transform, targetTab.transform, "Move objects to folder"); if (resetPositionOnMove) { Undo.RecordObject(obj.transform, "Zero Position"); obj.transform.localPosition = Vector3.zero; obj.transform.localRotation = Quaternion.identity; } } } } // ------------------------------------------------------------------------ // Helper Methods & Updating // ------------------------------------------------------------------------ private void Update() { if (isPinned) { UpdateTopLevelObjects(); UpdateSelectedObjects(); Repaint(); } } private static void LoadSettings() { if (settings != null) return; settings = AssetDatabase.LoadAssetAtPath("Assets/Scripts/Editor/HierarchyDecoratorSettings.asset"); if (settings == null) { settings = ScriptableObject.CreateInstance(); settings.defaultBackgroundColor = Color.gray; } } private static void UpdateSelectedObjects() { selectedObjects = new List(Selection.gameObjects); } private static void UpdateTopLevelObjects() { if (settings == null) LoadSettings(); topLevelObjects = new List(); foreach (GameObject obj in Resources.FindObjectsOfTypeAll()) { if (obj.hideFlags == HideFlags.None && HasColorInHierarchy(obj) && !obj.name.Contains("= Clean")) { topLevelObjects.Add(obj); Color color = CalculateColorForObject(obj); if (color != settings.defaultBackgroundColor) { if (!colorFilters.ContainsKey(color)) { colorFilters[color] = true; } } } } topLevelObjects = topLevelObjects.OrderBy(obj => GetHierarchyDepth(obj)).ThenBy(obj => obj.transform.GetSiblingIndex()).ToList(); } private static int GetHierarchyDepth(GameObject obj) { if (obj == null) return 0; int depth = 0; Transform current = obj.transform; while (current.parent != null) { depth++; current = current.parent; } return depth; } private void SetAllTabsActiveState(bool isActive) { foreach (var obj in topLevelObjects) { if (obj != null) obj.SetActive(isActive); } } private void CollapseAllInHierarchy() { foreach (GameObject obj in topLevelObjects) { if (obj != null && obj.transform.childCount > 0) { bool state = obj.activeSelf; obj.SetActive(false); obj.SetActive(state); } } } private static bool HasColorInHierarchy(GameObject obj) { return obj != null && obj.name.Contains("="); } private static Color CalculateColorForObject(GameObject obj) { Color color = settings != null ? settings.defaultBackgroundColor : Color.gray; if (settings != null && obj.name.Contains("=")) { int colorIdentifier; string[] parts = obj.name.Split('='); if (parts.Length > 0 && int.TryParse(parts[0].Trim(), out colorIdentifier)) { var scheme = settings.colorSchemes.FirstOrDefault(s => s.identifier == colorIdentifier); if (scheme != null) { color = obj.activeSelf ? scheme.activeBackgroundColor : scheme.inactiveBackgroundColor; } } } return color; } private void DrawOutline(Rect rect, float size, Color color) { EditorGUI.DrawRect(new Rect(rect.x, rect.y, rect.width, size), color); EditorGUI.DrawRect(new Rect(rect.x, rect.y + rect.height - size, rect.width, size), color); EditorGUI.DrawRect(new Rect(rect.x, rect.y, size, rect.height), color); EditorGUI.DrawRect(new Rect(rect.x + rect.width - size, rect.y, size, rect.height), color); } private Texture2D MakeTex(int width, int height, Color col) { Color[] pix = new Color[width * height]; for (int i = 0; i < pix.Length; i++) pix[i] = col; Texture2D result = new Texture2D(width, height); result.SetPixels(pix); result.Apply(); return result; } private Texture2D MakeColorIcon(Color color) { return MakeTex(12, 12, color); } }