Files
beyond/Assets/Scripts/Editor/MoveToWindow.cs
2025-11-27 01:51:39 +01:00

688 lines
22 KiB
C#

using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Linq;
public class MoveToWindow : EditorWindow
{
// ------------------------------------------------------------------------
// Core Variables
// ------------------------------------------------------------------------
private static List<GameObject> selectedObjects;
private static List<GameObject> topLevelObjects;
private static HierarchyDecoratorSettings settings;
// ------------------------------------------------------------------------
// UI State Variables
// ------------------------------------------------------------------------
private Vector2 scrollPosition;
private string filterText = "";
private bool isPinned = false;
private static Dictionary<Color, bool> colorFilters = new Dictionary<Color, bool>();
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<MoveToWindow>("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<GameObject> 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<HierarchyDecoratorSettings>("Assets/Scripts/Editor/HierarchyDecoratorSettings.asset");
if (settings == null)
{
settings = ScriptableObject.CreateInstance<HierarchyDecoratorSettings>();
settings.defaultBackgroundColor = Color.gray;
}
}
private static void UpdateSelectedObjects()
{
selectedObjects = new List<GameObject>(Selection.gameObjects);
}
private static void UpdateTopLevelObjects()
{
if (settings == null) LoadSettings();
topLevelObjects = new List<GameObject>();
foreach (GameObject obj in Resources.FindObjectsOfTypeAll<GameObject>())
{
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);
}
}