using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using TMPro;
using UnityEditor.SceneManagement;
///
/// Editor menu to build ProgrammedMovements UI hierarchy in the scene.
/// Run once via Tools > ProgrammedMovements > Setup UI Hierarchy.
/// The hierarchy is created in the scene (not at runtime) so you can modify it.
///
public static class ProgrammedMovementsSetup
{
[InitializeOnLoadMethod]
static void OnLoad()
{
EditorSceneManager.sceneOpened += (scene, mode) =>
{
if (Application.isPlaying) return;
var pmView = GameObject.Find("Views")?.transform?.Find("ProgrammedMovementsView");
if (pmView == null) return;
var canvas = pmView.Find("ProgrammedMovementsCanvas");
if (canvas == null)
{
SetupHierarchy();
EditorSceneManager.MarkSceneDirty(scene);
return;
}
var banner = canvas.Find("TimelineBanner");
if (banner != null && banner.Find("ConfigView") == null && banner.Find("TracksContainer") != null)
{
MigrateToNewLayout();
EditorSceneManager.MarkSceneDirty(scene);
}
};
}
const int TracksCount = 3;
const int TimelineDurationSeconds = 10;
[MenuItem("Tools/ProgrammedMovements/Setup UI Hierarchy")]
public static void SetupHierarchy()
{
var pmView = GameObject.Find("Views")?.transform?.Find("ProgrammedMovementsView");
if (pmView == null)
{
Debug.LogError("[ProgrammedMovementsSetup] Views/ProgrammedMovementsView not found.");
return;
}
var existing = pmView.Find("ProgrammedMovementsCanvas");
if (existing != null)
{
Debug.Log("[ProgrammedMovementsSetup] Hierarchy already exists. Use Tools > ProgrammedMovements > Force Rebuild UI to replace.");
return;
}
DoSetup(pmView);
}
[MenuItem("Tools/ProgrammedMovements/Force Rebuild UI")]
public static void ForceRebuildHierarchy()
{
var pmView = GameObject.Find("Views")?.transform?.Find("ProgrammedMovementsView");
if (pmView == null)
{
Debug.LogError("[ProgrammedMovementsSetup] Views/ProgrammedMovementsView not found.");
return;
}
var existing = pmView.Find("ProgrammedMovementsCanvas");
if (existing != null)
{
Object.DestroyImmediate(existing.gameObject);
}
DoSetup(pmView);
}
[MenuItem("Tools/ProgrammedMovements/Migrate to New Layout (add Config panel)")]
public static void MigrateToNewLayout()
{
var pmView = GameObject.Find("Views")?.transform?.Find("ProgrammedMovementsView");
if (pmView == null)
{
Debug.LogError("[ProgrammedMovementsSetup] Views/ProgrammedMovementsView not found.");
return;
}
var canvas = pmView.Find("ProgrammedMovementsCanvas");
if (canvas == null)
{
Debug.Log("[ProgrammedMovementsSetup] No canvas found. Run Setup UI Hierarchy first.");
return;
}
var banner = canvas.Find("TimelineBanner");
if (banner == null)
{
Debug.LogError("[ProgrammedMovementsSetup] TimelineBanner not found.");
return;
}
if (banner.Find("ConfigView") != null)
{
Debug.Log("[ProgrammedMovementsSetup] Already has ConfigView. No migration needed.");
return;
}
var bannerRect = banner.GetComponent();
if (bannerRect == null)
{
Debug.LogError("[ProgrammedMovementsSetup] TimelineBanner has no RectTransform.");
return;
}
Transform timeLabels = banner.Find("TimeLabels");
Transform verticalLines = banner.Find("VerticalLines");
Transform tracksContainer = banner.Find("TracksContainer");
var timelineViewExisting = banner.Find("TimelineView");
if (timeLabels == null && timelineViewExisting != null)
{
timeLabels = timelineViewExisting.Find("TimeLabels");
verticalLines = timelineViewExisting.Find("VerticalLines");
tracksContainer = timelineViewExisting.Find("TracksContainer");
}
if (timeLabels == null || verticalLines == null || tracksContainer == null)
{
Debug.LogError("[ProgrammedMovementsSetup] Old structure not found. Use Force Rebuild UI.");
return;
}
GameObject timelineViewGO;
if (timelineViewExisting != null)
{
timelineViewGO = timelineViewExisting.gameObject;
}
else
{
timelineViewGO = new GameObject("TimelineView");
Undo.RegisterCreatedObjectUndo(timelineViewGO, "Migrate PM");
timelineViewGO.transform.SetParent(banner, false);
var tr = timelineViewGO.AddComponent();
tr.anchorMin = Vector2.zero;
tr.anchorMax = Vector2.one;
tr.offsetMin = Vector2.zero;
tr.offsetMax = Vector2.zero;
Undo.SetTransformParent(timeLabels, timelineViewGO.transform, "Migrate PM");
Undo.SetTransformParent(verticalLines, timelineViewGO.transform, "Migrate PM");
Undo.SetTransformParent(tracksContainer, timelineViewGO.transform, "Migrate PM");
}
var configViewGO = new GameObject("ConfigView");
Undo.RegisterCreatedObjectUndo(configViewGO, "Migrate PM");
configViewGO.transform.SetParent(banner, false);
configViewGO.SetActive(false);
var configViewRect = configViewGO.AddComponent();
configViewRect.anchorMin = Vector2.zero;
configViewRect.anchorMax = Vector2.one;
configViewRect.offsetMin = Vector2.zero;
configViewRect.offsetMax = Vector2.zero;
var configContent = new GameObject("ConfigContent");
configContent.transform.SetParent(configViewRect, false);
var configContentRect = configContent.AddComponent();
configContentRect.anchorMin = Vector2.zero;
configContentRect.anchorMax = Vector2.one;
configContentRect.offsetMin = new Vector2(12, 8);
configContentRect.offsetMax = new Vector2(-12, -8);
var configVertical = configContent.AddComponent();
configVertical.spacing = 8;
configVertical.padding = new RectOffset(8, 8, 8, 8);
configVertical.childAlignment = TextAnchor.MiddleLeft;
configVertical.childControlWidth = true;
configVertical.childControlHeight = true;
configVertical.childForceExpandWidth = true;
configVertical.childForceExpandHeight = false;
var row1 = CreateConfigRow1(configContent.transform);
var row2 = CreateConfigRow2(configContent.transform);
var tracksRect = tracksContainer.GetComponent();
if (tracksRect == null)
{
Debug.LogError("[ProgrammedMovementsSetup] TracksContainer has no RectTransform.");
return;
}
for (int t = 0; t < TracksCount; t++)
{
if (t >= tracksRect.childCount) break;
var track = tracksRect.GetChild(t);
if (track == null) continue;
var blocks = track.Find("Blocks");
if (blocks != null)
{
if (blocks.GetComponent() == null)
{
var img = blocks.gameObject.AddComponent();
img.color = new Color(0, 0, 0, 0);
}
if (blocks.GetComponent() == null)
blocks.gameObject.AddComponent();
}
}
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
Debug.Log("[ProgrammedMovementsSetup] Migrated to new layout. Save the scene. Enter Play mode to see Config panel when adding/editing blocks.");
}
static void DoSetup(Transform pmView)
{
var canvasGO = new GameObject("ProgrammedMovementsCanvas");
Undo.RegisterCreatedObjectUndo(canvasGO, "Create PM Canvas");
canvasGO.transform.SetParent(pmView, false);
var canvasRect = canvasGO.AddComponent();
canvasRect.anchorMin = Vector2.zero;
canvasRect.anchorMax = Vector2.one;
canvasRect.offsetMin = Vector2.zero;
canvasRect.offsetMax = Vector2.zero;
canvasGO.AddComponent