384 lines
15 KiB
C#
384 lines
15 KiB
C#
using System;
|
|
using System.IO;
|
|
using Codely.Newtonsoft.Json.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
using UnityTcp.Editor.Helpers;
|
|
|
|
namespace UnityTcp.Editor.Tools
|
|
{
|
|
/// <summary>
|
|
/// [EXPERIMENTAL] Handles UI Toolkit operations (UXML, USS, PanelSettings).
|
|
/// </summary>
|
|
public static class ManageUIToolkit
|
|
{
|
|
public static object HandleCommand(JObject @params)
|
|
{
|
|
string action = @params["action"]?.ToString().ToLower();
|
|
if (string.IsNullOrEmpty(action))
|
|
{
|
|
return Response.Error("Action parameter is required.");
|
|
}
|
|
|
|
try
|
|
{
|
|
switch (action)
|
|
{
|
|
case "ensure_panel_settings_asset":
|
|
return EnsurePanelSettingsAsset(@params);
|
|
case "link_uss_to_uxml":
|
|
return LinkUssToUxml(@params);
|
|
case "create_uxml":
|
|
return CreateUxml(@params);
|
|
case "create_uss":
|
|
return CreateUss(@params);
|
|
default:
|
|
return Response.Error(
|
|
$"Unknown action: '{action}'. Valid actions: ensure_panel_settings_asset, link_uss_to_uxml, create_uxml, create_uss."
|
|
);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"[ManageUIToolkit] Action '{action}' failed: {e}");
|
|
return Response.Error($"[EXPERIMENTAL] UI Toolkit operation failed: {e.Message}");
|
|
}
|
|
}
|
|
|
|
private static object EnsurePanelSettingsAsset(JObject @params)
|
|
{
|
|
try
|
|
{
|
|
var writeCheck = WriteGuard.CheckWriteAllowed("ensure_panel_settings_asset");
|
|
if (writeCheck != null) return writeCheck;
|
|
|
|
string path = @params["path"]?.ToString();
|
|
if (string.IsNullOrEmpty(path))
|
|
return Response.Error("'path' parameter required.");
|
|
|
|
if (!path.EndsWith(".asset"))
|
|
path += ".asset";
|
|
|
|
// Check if already exists
|
|
var existingAsset = AssetDatabase.LoadAssetAtPath<PanelSettings>(path);
|
|
if (existingAsset != null)
|
|
{
|
|
return new
|
|
{
|
|
success = true,
|
|
message = "[EXPERIMENTAL] PanelSettings asset already exists.",
|
|
data = new { path = path, alreadyExists = true },
|
|
state_delta = StateComposer.CreateAssetDelta(new[] {
|
|
new { path = path, imported = false, hasMeta = true }
|
|
})
|
|
};
|
|
}
|
|
|
|
// Create directory if needed
|
|
string dir = Path.GetDirectoryName(path);
|
|
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
|
{
|
|
Directory.CreateDirectory(dir);
|
|
}
|
|
|
|
// Create PanelSettings asset
|
|
var panelSettings = ScriptableObject.CreateInstance<PanelSettings>();
|
|
AssetDatabase.CreateAsset(panelSettings, path);
|
|
AssetDatabase.SaveAssets();
|
|
StateComposer.IncrementRevision();
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = "[EXPERIMENTAL] PanelSettings asset created.",
|
|
data = new { path = path, alreadyExists = false },
|
|
state_delta = StateComposer.CreateAssetDelta(new[] {
|
|
new { path = path, imported = true, hasMeta = true }
|
|
})
|
|
};
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
return Response.Error($"[EXPERIMENTAL] Failed to ensure PanelSettings asset: {e.Message}");
|
|
}
|
|
}
|
|
|
|
private static object LinkUssToUxml(JObject @params)
|
|
{
|
|
try
|
|
{
|
|
var writeCheck = WriteGuard.CheckWriteAllowed("link_uss_to_uxml");
|
|
if (writeCheck != null) return writeCheck;
|
|
|
|
// Support both parameter naming conventions: uxml/uss and uxml_path/uss_path,
|
|
// as well as GUID-based references via uxml_guid/uss_guid.
|
|
string uxmlShorthand = @params["uxml"]?.ToString();
|
|
string uxmlPathParam = @params["uxml_path"]?.ToString();
|
|
string uxmlGuidParam = @params["uxml_guid"]?.ToString();
|
|
|
|
string ussShorthand = @params["uss"]?.ToString();
|
|
string ussPathParam = @params["uss_path"]?.ToString();
|
|
string ussGuidParam = @params["uss_guid"]?.ToString();
|
|
|
|
bool hasUxmlIdentifier =
|
|
!string.IsNullOrEmpty(uxmlShorthand)
|
|
|| !string.IsNullOrEmpty(uxmlPathParam)
|
|
|| !string.IsNullOrEmpty(uxmlGuidParam);
|
|
bool hasUssIdentifier =
|
|
!string.IsNullOrEmpty(ussShorthand)
|
|
|| !string.IsNullOrEmpty(ussPathParam)
|
|
|| !string.IsNullOrEmpty(ussGuidParam);
|
|
|
|
if (!hasUxmlIdentifier)
|
|
return Response.Error("Either 'uxml', 'uxml_path', or 'uxml_guid' parameter is required.");
|
|
if (!hasUssIdentifier)
|
|
return Response.Error("Either 'uss', 'uss_path', or 'uss_guid' parameter is required.");
|
|
|
|
string uxmlGuidUsed;
|
|
string ussGuidUsed;
|
|
|
|
string uxmlPath = ResolveAssetPath(uxmlShorthand, uxmlPathParam, uxmlGuidParam, out uxmlGuidUsed);
|
|
string ussPath = ResolveAssetPath(ussShorthand, ussPathParam, ussGuidParam, out ussGuidUsed);
|
|
|
|
if (string.IsNullOrEmpty(uxmlPath))
|
|
{
|
|
if (!string.IsNullOrEmpty(uxmlGuidUsed))
|
|
{
|
|
return Response.Error($"UXML asset not found for GUID: {uxmlGuidUsed}");
|
|
}
|
|
|
|
return Response.Error("UXML path could not be resolved.");
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(ussPath))
|
|
{
|
|
if (!string.IsNullOrEmpty(ussGuidUsed))
|
|
{
|
|
return Response.Error($"USS asset not found for GUID: {ussGuidUsed}");
|
|
}
|
|
|
|
return Response.Error("USS path could not be resolved.");
|
|
}
|
|
|
|
var uxmlAsset = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(uxmlPath);
|
|
if (uxmlAsset == null)
|
|
return Response.Error($"UXML not found at: {uxmlPath}");
|
|
|
|
var ussAsset = AssetDatabase.LoadAssetAtPath<StyleSheet>(ussPath);
|
|
if (ussAsset == null)
|
|
return Response.Error($"USS not found at: {ussPath}");
|
|
|
|
// Read UXML file content
|
|
string uxmlContent = File.ReadAllText(uxmlPath);
|
|
|
|
// Check if USS is already linked
|
|
string ussFileName = Path.GetFileName(ussPath);
|
|
if (uxmlContent.Contains($"src=\"{ussFileName}\""))
|
|
{
|
|
return new
|
|
{
|
|
success = true,
|
|
message = "[EXPERIMENTAL] USS already linked to UXML.",
|
|
data = new { uxml = uxmlPath, uss = ussPath, alreadyLinked = true },
|
|
state_delta = StateComposer.CreateAssetDelta(new[] {
|
|
new { path = uxmlPath, imported = false, hasMeta = true }
|
|
})
|
|
};
|
|
}
|
|
|
|
// Add USS reference to UXML
|
|
// Insert after <ui:UXML> tag
|
|
int insertPos = uxmlContent.IndexOf("<ui:UXML");
|
|
if (insertPos >= 0)
|
|
{
|
|
insertPos = uxmlContent.IndexOf('>', insertPos) + 1;
|
|
string styleTag = $"\n <Style src=\"{ussFileName}\" />";
|
|
uxmlContent = uxmlContent.Insert(insertPos, styleTag);
|
|
File.WriteAllText(uxmlPath, uxmlContent);
|
|
AssetDatabase.ImportAsset(uxmlPath);
|
|
StateComposer.IncrementRevision();
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = "[EXPERIMENTAL] USS linked to UXML.",
|
|
data = new { uxml = uxmlPath, uss = ussPath, alreadyLinked = false },
|
|
state_delta = StateComposer.CreateAssetDelta(new[] {
|
|
new { path = uxmlPath, imported = true, hasMeta = true }
|
|
})
|
|
};
|
|
}
|
|
else
|
|
{
|
|
return Response.Error("Failed to parse UXML file.");
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
return Response.Error($"[EXPERIMENTAL] Failed to link USS to UXML: {e.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves an asset path from either a shorthand (path or GUID), an explicit path, or a GUID.
|
|
/// - If explicit path is provided, it is returned as-is.
|
|
/// - If shorthand contains '/', it is treated as a path.
|
|
/// - Otherwise shorthand / guid are treated as GUIDs and resolved via AssetDatabase.GUIDToAssetPath.
|
|
/// Returns null if the asset path cannot be resolved.
|
|
/// </summary>
|
|
private static string ResolveAssetPath(string shorthand, string pathParam, string guidParam, out string guidUsed)
|
|
{
|
|
guidUsed = null;
|
|
|
|
// Prefer explicit path if provided
|
|
if (!string.IsNullOrEmpty(pathParam))
|
|
{
|
|
return pathParam;
|
|
}
|
|
|
|
// Handle shorthand: path or GUID
|
|
if (!string.IsNullOrEmpty(shorthand))
|
|
{
|
|
if (shorthand.Contains("/"))
|
|
{
|
|
// Looks like a path (ValidateToolParams already enforced "Assets/" prefix when appropriate)
|
|
return shorthand;
|
|
}
|
|
|
|
// Treat shorthand as GUID
|
|
guidUsed = shorthand;
|
|
string resolvedFromShorthand = AssetDatabase.GUIDToAssetPath(shorthand);
|
|
if (!string.IsNullOrEmpty(resolvedFromShorthand))
|
|
{
|
|
return resolvedFromShorthand;
|
|
}
|
|
|
|
// Fall through and let guidParam try (could be different)
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(guidParam))
|
|
{
|
|
guidUsed = guidParam;
|
|
string resolvedFromGuid = AssetDatabase.GUIDToAssetPath(guidParam);
|
|
if (!string.IsNullOrEmpty(resolvedFromGuid))
|
|
{
|
|
return resolvedFromGuid;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static object CreateUxml(JObject @params)
|
|
{
|
|
try
|
|
{
|
|
var writeCheck = WriteGuard.CheckWriteAllowed("create_uxml");
|
|
if (writeCheck != null) return writeCheck;
|
|
|
|
string path = @params["path"]?.ToString();
|
|
if (string.IsNullOrEmpty(path))
|
|
return Response.Error("'path' parameter required.");
|
|
|
|
if (!path.EndsWith(".uxml"))
|
|
path += ".uxml";
|
|
|
|
if (File.Exists(path))
|
|
return Response.Error($"UXML file already exists at: {path}");
|
|
|
|
string dir = Path.GetDirectoryName(path);
|
|
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
|
{
|
|
Directory.CreateDirectory(dir);
|
|
}
|
|
|
|
string template = @"<?xml version=""1.0"" encoding=""utf-8""?>
|
|
<ui:UXML
|
|
xmlns:ui=""UnityEngine.UIElements""
|
|
xmlns:uie=""UnityEditor.UIElements"">
|
|
<ui:VisualElement name=""root"" style=""flex-grow: 1;"">
|
|
<ui:Label text=""Hello UI Toolkit"" name=""title-label"" />
|
|
</ui:VisualElement>
|
|
</ui:UXML>";
|
|
|
|
File.WriteAllText(path, template);
|
|
AssetDatabase.ImportAsset(path);
|
|
StateComposer.IncrementRevision();
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = "[EXPERIMENTAL] UXML file created.",
|
|
data = new { path = path },
|
|
state_delta = StateComposer.CreateAssetDelta(new[] {
|
|
new { path = path, imported = true, hasMeta = true }
|
|
})
|
|
};
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
return Response.Error($"[EXPERIMENTAL] Failed to create UXML: {e.Message}");
|
|
}
|
|
}
|
|
|
|
private static object CreateUss(JObject @params)
|
|
{
|
|
try
|
|
{
|
|
var writeCheck = WriteGuard.CheckWriteAllowed("create_uss");
|
|
if (writeCheck != null) return writeCheck;
|
|
|
|
string path = @params["path"]?.ToString();
|
|
if (string.IsNullOrEmpty(path))
|
|
return Response.Error("'path' parameter required.");
|
|
|
|
if (!path.EndsWith(".uss"))
|
|
path += ".uss";
|
|
|
|
if (File.Exists(path))
|
|
return Response.Error($"USS file already exists at: {path}");
|
|
|
|
string dir = Path.GetDirectoryName(path);
|
|
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
|
{
|
|
Directory.CreateDirectory(dir);
|
|
}
|
|
|
|
string template = @"/* UI Toolkit Style Sheet */
|
|
.root {
|
|
flex-grow: 1;
|
|
background-color: rgb(56, 56, 56);
|
|
}
|
|
|
|
#title-label {
|
|
font-size: 20px;
|
|
-unity-font-style: bold;
|
|
color: rgb(255, 255, 255);
|
|
padding: 10px;
|
|
}
|
|
";
|
|
|
|
File.WriteAllText(path, template);
|
|
AssetDatabase.ImportAsset(path);
|
|
StateComposer.IncrementRevision();
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = "[EXPERIMENTAL] USS file created.",
|
|
data = new { path = path },
|
|
state_delta = StateComposer.CreateAssetDelta(new[] {
|
|
new { path = path, imported = true, hasMeta = true }
|
|
})
|
|
};
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
return Response.Error($"[EXPERIMENTAL] Failed to create USS: {e.Message}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|