chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:49:17 +08:00
commit 7243d5823b
2201 changed files with 257291 additions and 0 deletions
@@ -0,0 +1,803 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MCPForUnity.Editor.Clients;
using MCPForUnity.Editor.Constants;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Models;
using MCPForUnity.Editor.Services;
using MCPForUnity.Editor.Services.Transport;
using MCPForUnity.Editor.Tools;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace MCPForUnity.Editor.Windows.Components.Tools
{
/// <summary>
/// Controller for the Tools section inside the MCP for Unity editor window.
/// Provides discovery, filtering, and per-tool enablement toggles.
/// Tools are grouped by their Group property (core first, then alphabetical).
/// </summary>
public class McpToolsSection
{
private readonly Dictionary<string, Toggle> toolToggleMap = new();
private Toggle projectScopedToolsToggle;
private Label summaryLabel;
private Label noteLabel;
private Button enableAllButton;
private Button disableAllButton;
private Button rescanButton;
private Button reconfigureButton;
private VisualElement categoryContainer;
private List<ToolMetadata> allTools = new();
private readonly Dictionary<string, Toggle> groupToggleMap = new();
private readonly List<(Foldout foldout, string title, List<ToolMetadata> tools)> foldoutEntries = new();
/// <summary>Human-friendly names for tool groups shown in the UI.</summary>
private static readonly Dictionary<string, string> GroupDisplayNames = new(StringComparer.OrdinalIgnoreCase)
{
{ "core", "Core Tools" },
{ "vfx", "VFX & Shaders" },
{ "animation", "Animation" },
{ "ui", "UI Toolkit" },
{ "scripting_ext", "Scripting Extensions" },
{ "testing", "Testing" },
{ "probuilder", "ProBuilder — Experimental" },
{ "profiling", "Profiling & Frame Debugger" },
};
public VisualElement Root { get; }
public McpToolsSection(VisualElement root)
{
Root = root;
CacheUIElements();
RegisterCallbacks();
}
private void CacheUIElements()
{
projectScopedToolsToggle = Root.Q<Toggle>("project-scoped-tools-toggle");
summaryLabel = Root.Q<Label>("tools-summary");
noteLabel = Root.Q<Label>("tools-note");
enableAllButton = Root.Q<Button>("enable-all-button");
disableAllButton = Root.Q<Button>("disable-all-button");
rescanButton = Root.Q<Button>("rescan-button");
reconfigureButton = Root.Q<Button>("reconfigure-button");
categoryContainer = Root.Q<VisualElement>("tool-category-container");
}
private void RegisterCallbacks()
{
if (projectScopedToolsToggle != null)
{
projectScopedToolsToggle.value = EditorPrefs.GetBool(
EditorPrefKeys.ProjectScopedToolsLocalHttp,
false
);
projectScopedToolsToggle.tooltip = "When enabled, register project-scoped tools with HTTP Local and stdio transports. Allows per-project tool customization.";
projectScopedToolsToggle.RegisterValueChangedCallback(evt =>
{
EditorPrefs.SetBool(EditorPrefKeys.ProjectScopedToolsLocalHttp, evt.newValue);
});
}
if (enableAllButton != null)
{
enableAllButton.AddToClassList("tool-action-button");
enableAllButton.style.marginRight = 4;
enableAllButton.clicked += () => SetAllToolsState(true);
}
if (disableAllButton != null)
{
disableAllButton.AddToClassList("tool-action-button");
disableAllButton.style.marginRight = 4;
disableAllButton.clicked += () => SetAllToolsState(false);
}
if (rescanButton != null)
{
rescanButton.AddToClassList("tool-action-button");
rescanButton.clicked += () =>
{
McpLog.Info("Rescanning MCP tools from the editor window.");
MCPServiceLocator.ToolDiscovery.InvalidateCache();
Refresh();
};
}
if (reconfigureButton != null)
{
reconfigureButton.AddToClassList("tool-action-button");
reconfigureButton.clicked += OnReconfigureClientsClicked;
}
}
/// <summary>
/// Rebuilds the tool list and synchronises toggle states.
/// Tools are displayed in group-based foldouts: core first, then other
/// groups alphabetically. Custom (non-built-in) tools appear in a
/// separate "Custom Tools" foldout at the bottom.
/// </summary>
public void Refresh()
{
toolToggleMap.Clear();
groupToggleMap.Clear();
foldoutEntries.Clear();
categoryContainer?.Clear();
var service = MCPServiceLocator.ToolDiscovery;
allTools = service.DiscoverAllTools()
.OrderBy(tool => tool.Name, StringComparer.OrdinalIgnoreCase)
.ToList();
bool hasTools = allTools.Count > 0;
enableAllButton?.SetEnabled(hasTools);
disableAllButton?.SetEnabled(hasTools);
if (noteLabel != null)
{
noteLabel.style.display = hasTools ? DisplayStyle.Flex : DisplayStyle.None;
if (hasTools)
{
bool isHttp = EditorConfigurationCache.Instance.UseHttpTransport;
noteLabel.text = isHttp
? "Changes apply after reconnecting or re-registering tools."
: "Stdio mode: toggles sync at startup. After changing toggles, ask the AI to run manage_tools with action 'sync' to refresh.";
}
}
if (!hasTools)
{
AddInfoLabel("No MCP tools found. Add classes decorated with [McpForUnityTool] to expose tools.");
UpdateSummary();
return;
}
// Partition into built-in and custom
var builtInTools = allTools.Where(IsBuiltIn).ToList();
var customTools = allTools.Where(tool => !IsBuiltIn(tool)).ToList();
// Group built-in tools by their Group property
var grouped = builtInTools
.GroupBy(t => t.Group ?? "core")
.ToDictionary(g => g.Key, g => g.OrderBy(t => t.Name, StringComparer.OrdinalIgnoreCase).ToList());
// Render "core" first, then remaining groups alphabetically
if (grouped.TryGetValue("core", out var coreTools))
{
BuildCategory(GetGroupDisplayName("core"), "group-core", coreTools);
grouped.Remove("core");
}
foreach (var kvp in grouped.OrderBy(k => k.Key, StringComparer.OrdinalIgnoreCase))
{
BuildCategory(GetGroupDisplayName(kvp.Key), $"group-{kvp.Key}", kvp.Value);
}
// Custom tools at the bottom
if (customTools.Count > 0)
{
BuildCategory("Custom Tools", "custom", customTools);
}
UpdateSummary();
}
private static string GetGroupDisplayName(string group)
{
if (GroupDisplayNames.TryGetValue(group, out var displayName))
return displayName;
// Fallback: capitalize first letter
return string.IsNullOrEmpty(group)
? "Other"
: char.ToUpper(group[0]) + group.Substring(1);
}
private void BuildCategory(string title, string prefsSuffix, IEnumerable<ToolMetadata> tools)
{
var toolList = tools.ToList();
if (toolList.Count == 0)
{
return;
}
bool isExperimental = string.Equals(prefsSuffix, "group-probuilder", StringComparison.OrdinalIgnoreCase);
int enabledCount = toolList.Count(t => MCPServiceLocator.ToolDiscovery.IsToolEnabled(t.Name));
// Default foldout state: core is open, others collapsed
bool defaultOpen = prefsSuffix == "group-core";
var foldout = new Foldout
{
text = $"{title} ({enabledCount}/{toolList.Count})",
value = EditorPrefs.GetBool(EditorPrefKeys.ToolFoldoutStatePrefix + prefsSuffix, defaultOpen)
};
foldout.RegisterValueChangedCallback(evt =>
{
if (evt.target != foldout) return;
EditorPrefs.SetBool(EditorPrefKeys.ToolFoldoutStatePrefix + prefsSuffix, evt.newValue);
});
// Add a checkbox into the foldout header to toggle all tools in this group
bool allEnabled = enabledCount == toolList.Count;
var groupCheckbox = new Toggle { value = allEnabled };
groupCheckbox.AddToClassList("group-header-checkbox");
groupCheckbox.tooltip = $"Toggle all tools in \"{title}\" on or off.";
// Prevent the click from propagating to the foldout expand/collapse toggle
groupCheckbox.RegisterCallback<ClickEvent>(evt => evt.StopPropagation());
groupCheckbox.RegisterValueChangedCallback(evt =>
{
evt.StopPropagation();
SetGroupToolsState(toolList, evt.newValue, foldout, title);
});
// Insert the checkbox into the foldout's own header toggle element
foldout.Q<Toggle>()?.Add(groupCheckbox);
groupToggleMap[prefsSuffix] = groupCheckbox;
foreach (var tool in toolList)
{
foldout.Add(CreateToolRow(tool));
}
if (isExperimental)
{
var warning = new HelpBox(
"ProBuilder support is experimental. Mesh editing operations may produce " +
"unexpected results on complex topologies. Always save your scene before " +
"performing destructive operations.",
HelpBoxMessageType.Warning);
warning.style.marginTop = 4;
warning.style.marginBottom = 2;
foldout.Insert(0, warning);
}
foldoutEntries.Add((foldout, title, toolList));
categoryContainer?.Add(foldout);
}
private VisualElement CreateToolRow(ToolMetadata tool)
{
var row = new VisualElement();
row.AddToClassList("tool-item");
var header = new VisualElement();
header.AddToClassList("tool-item-header");
var toggle = new Toggle(tool.Name)
{
value = MCPServiceLocator.ToolDiscovery.IsToolEnabled(tool.Name)
};
toggle.AddToClassList("tool-item-toggle");
toggle.tooltip = string.IsNullOrWhiteSpace(tool.Description) ? tool.Name : tool.Description;
toggle.RegisterValueChangedCallback(evt =>
{
HandleToggleChange(tool, evt.newValue);
});
toolToggleMap[tool.Name] = toggle;
header.Add(toggle);
var tagsContainer = new VisualElement();
tagsContainer.AddToClassList("tool-tags");
bool defaultEnabled = tool.AutoRegister || tool.IsBuiltIn;
tagsContainer.Add(CreateTag(defaultEnabled ? "On by default" : "Off by default"));
tagsContainer.Add(CreateTag(tool.StructuredOutput ? "Structured output" : "Free-form"));
if (tool.RequiresPolling)
{
tagsContainer.Add(CreateTag($"Polling: {tool.PollAction}"));
}
header.Add(tagsContainer);
row.Add(header);
// Skip auto-generated placeholder descriptions like "Tool: find_gameobjects"
if (!string.IsNullOrWhiteSpace(tool.Description)
&& !tool.Description.StartsWith("Tool: ", StringComparison.OrdinalIgnoreCase))
{
var description = new Label(tool.Description);
description.AddToClassList("tool-item-description");
row.Add(description);
}
if (tool.Parameters != null && tool.Parameters.Count > 0)
{
var paramSummary = string.Join(", ", tool.Parameters.Select(p =>
$"{p.Name}{(p.Required ? string.Empty : " (optional)")}: {p.Type}"));
var parametersLabel = new Label(paramSummary);
parametersLabel.AddToClassList("tool-parameters");
row.Add(parametersLabel);
}
if (IsManageCameraTool(tool))
{
row.Add(CreateManageSceneActions());
}
if (IsBatchExecuteTool(tool))
{
row.Add(CreateBatchExecuteSettings());
}
return row;
}
private void HandleToggleChange(
ToolMetadata tool,
bool enabled,
bool updateSummary = true,
bool reregisterTools = true)
{
MCPServiceLocator.ToolDiscovery.SetToolEnabled(tool.Name, enabled);
if (updateSummary)
{
UpdateSummary();
UpdateFoldoutHeaders();
SyncGroupToggles();
}
if (reregisterTools)
{
// Trigger tool reregistration with connected MCP server
ReregisterToolsAsync();
}
}
private void ReregisterToolsAsync()
{
// Fire and forget - don't block UI thread
var transportManager = MCPServiceLocator.TransportManager;
var client = transportManager.GetClient(TransportMode.Http);
if (client == null || !client.IsConnected)
{
return;
}
_ = Task.Run(async () =>
{
try
{
await client.ReregisterToolsAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
McpLog.Warn($"Failed to reregister tools: {ex}");
}
});
}
private void SetAllToolsState(bool enabled)
{
bool hasChanges = false;
foreach (var tool in allTools)
{
if (!toolToggleMap.TryGetValue(tool.Name, out var toggle))
{
bool currentEnabled = MCPServiceLocator.ToolDiscovery.IsToolEnabled(tool.Name);
if (currentEnabled != enabled)
{
MCPServiceLocator.ToolDiscovery.SetToolEnabled(tool.Name, enabled);
hasChanges = true;
}
continue;
}
if (toggle.value == enabled)
{
continue;
}
toggle.SetValueWithoutNotify(enabled);
HandleToggleChange(tool, enabled, updateSummary: false, reregisterTools: false);
hasChanges = true;
}
UpdateSummary();
UpdateFoldoutHeaders();
SyncGroupToggles();
if (hasChanges)
{
// Trigger a single reregistration after bulk change
ReregisterToolsAsync();
}
}
private void SetGroupToolsState(List<ToolMetadata> groupTools, bool enabled, Foldout foldout, string title)
{
bool hasChanges = false;
foreach (var tool in groupTools)
{
if (toolToggleMap.TryGetValue(tool.Name, out var toggle))
{
if (toggle.value != enabled)
{
toggle.SetValueWithoutNotify(enabled);
HandleToggleChange(tool, enabled, updateSummary: false, reregisterTools: false);
hasChanges = true;
}
}
else
{
bool currentEnabled = MCPServiceLocator.ToolDiscovery.IsToolEnabled(tool.Name);
if (currentEnabled != enabled)
{
MCPServiceLocator.ToolDiscovery.SetToolEnabled(tool.Name, enabled);
hasChanges = true;
}
}
}
// Update the foldout header count
int enabledCount = groupTools.Count(t => MCPServiceLocator.ToolDiscovery.IsToolEnabled(t.Name));
foldout.text = $"{title} ({enabledCount}/{groupTools.Count})";
// Sync global group toggles after group change
SyncGroupToggles();
UpdateSummary();
if (hasChanges)
{
ReregisterToolsAsync();
}
}
/// <summary>
/// Synchronises group toggle checkmarks with actual tool states.
/// Called after individual tool toggles change so the group toggle
/// stays accurate.
/// </summary>
private void SyncGroupToggles()
{
// We need the grouped tool lists to check states.
var builtInTools = allTools.Where(IsBuiltIn).ToList();
var grouped = builtInTools
.GroupBy(t => t.Group ?? "core")
.ToDictionary(g => g.Key, g => g.ToList());
var customTools = allTools.Where(t => !IsBuiltIn(t)).ToList();
foreach (var kvp in groupToggleMap)
{
List<ToolMetadata> groupTools;
if (kvp.Key == "custom")
{
groupTools = customTools;
}
else
{
string groupKey = kvp.Key.StartsWith("group-") ? kvp.Key.Substring(6) : kvp.Key;
if (!grouped.TryGetValue(groupKey, out groupTools))
continue;
}
bool allEnabled = groupTools.All(t => MCPServiceLocator.ToolDiscovery.IsToolEnabled(t.Name));
kvp.Value.SetValueWithoutNotify(allEnabled);
}
}
private void OnReconfigureClientsClicked()
{
try
{
// Re-register tools with the server (HTTP mode)
ReregisterToolsAsync();
// Reconfigure all already-configured clients.
// For CLI-based clients Configure() is a toggle (unregister if
// configured, register if not), so we call it twice: first to
// unregister, then to re-register with the updated tool set.
var clients = MCPServiceLocator.Client.GetAllClients();
int success = 0;
int skipped = 0;
var messages = new List<string>();
foreach (var client in clients)
{
try
{
client.CheckStatus(attemptAutoRewrite: false);
if (client.Status != McpStatus.Configured)
{
skipped++;
continue;
}
if (client is ClaudeCliMcpConfigurator)
{
// Toggle off (unregister), then toggle on (register)
MCPServiceLocator.Client.ConfigureClient(client);
MCPServiceLocator.Client.ConfigureClient(client);
}
else
{
// JSON-file clients: rewrite is idempotent
MCPServiceLocator.Client.ConfigureClient(client);
}
success++;
messages.Add($"✓ {client.DisplayName}: Reconfigured");
}
catch (Exception ex)
{
messages.Add($"⚠ {client.DisplayName}: {ex.Message}");
}
}
string header = $"Reconfigured {success} client(s), skipped {skipped}.";
string body = messages.Count > 0
? header + "\n\n" + string.Join("\n", messages)
: header;
EditorUtility.DisplayDialog("Reconfigure Clients", body, "OK");
}
catch (Exception ex)
{
EditorUtility.DisplayDialog("Reconfigure Failed", ex.Message, "OK");
McpLog.Error($"Reconfigure failed: {ex.Message}");
}
}
private void UpdateSummary()
{
if (summaryLabel == null)
{
return;
}
if (allTools.Count == 0)
{
summaryLabel.text = "No MCP tools discovered.";
return;
}
int enabledCount = allTools.Count(tool => MCPServiceLocator.ToolDiscovery.IsToolEnabled(tool.Name));
summaryLabel.text = $"{enabledCount} of {allTools.Count} tools will register with connected clients.";
}
private void UpdateFoldoutHeaders()
{
foreach (var (foldout, title, tools) in foldoutEntries)
{
int enabledCount = tools.Count(t => MCPServiceLocator.ToolDiscovery.IsToolEnabled(t.Name));
foldout.text = $"{title} ({enabledCount}/{tools.Count})";
}
}
private void AddInfoLabel(string message)
{
var label = new Label(message);
label.AddToClassList("help-text");
categoryContainer?.Add(label);
}
private VisualElement CreateManageSceneActions()
{
var actions = new VisualElement();
actions.AddToClassList("tool-item-actions");
var gameViewButton = new Button(OnManageSceneScreenshotClicked)
{
text = "Game View"
};
gameViewButton.AddToClassList("tool-action-button");
gameViewButton.style.marginTop = 4;
gameViewButton.tooltip = "Capture a game camera screenshot. Default: Assets/Screenshots (configurable in Advanced).";
var sceneViewButton = new Button(OnSceneViewScreenshotClicked)
{
text = "Scene View"
};
sceneViewButton.AddToClassList("tool-action-button");
sceneViewButton.style.marginTop = 4;
sceneViewButton.style.marginLeft = 4;
sceneViewButton.tooltip = "Capture the active Scene View viewport. Default: Assets/Screenshots (configurable in Advanced).";
var multiviewButton = new Button(OnManageSceneMultiviewClicked)
{
text = "Multiview"
};
multiviewButton.AddToClassList("tool-action-button");
multiviewButton.style.marginTop = 4;
multiviewButton.style.marginLeft = 4;
multiviewButton.tooltip = "Capture a 6-angle contact sheet around the scene centre. Default: Assets/Screenshots (configurable in Advanced).";
var captureLabel = new Label("Capture:");
captureLabel.style.marginTop = 6;
captureLabel.style.unityFontStyleAndWeight = UnityEngine.FontStyle.Normal;
var row = new VisualElement();
row.style.flexDirection = FlexDirection.Row;
row.style.flexWrap = Wrap.Wrap;
row.Add(gameViewButton);
row.Add(sceneViewButton);
row.Add(multiviewButton);
actions.Add(captureLabel);
actions.Add(row);
return actions;
}
private VisualElement CreateBatchExecuteSettings()
{
var container = new VisualElement();
container.AddToClassList("tool-item-actions");
container.style.flexDirection = FlexDirection.Row;
container.style.alignItems = Align.Center;
container.style.marginTop = 4;
var label = new Label("Max commands per batch:");
label.style.marginRight = 8;
label.style.unityFontStyleAndWeight = UnityEngine.FontStyle.Normal;
container.Add(label);
int currentValue = EditorPrefs.GetInt(
EditorPrefKeys.BatchExecuteMaxCommands,
BatchExecute.DefaultMaxCommandsPerBatch
);
var field = new IntegerField
{
value = Math.Clamp(currentValue, 1, BatchExecute.AbsoluteMaxCommandsPerBatch),
style = { width = 60 }
};
field.tooltip = $"Number of commands allowed per batch_execute call (1{BatchExecute.AbsoluteMaxCommandsPerBatch}). Default: {BatchExecute.DefaultMaxCommandsPerBatch}.";
field.RegisterValueChangedCallback(evt =>
{
int clamped = Math.Clamp(evt.newValue, 1, BatchExecute.AbsoluteMaxCommandsPerBatch);
if (clamped != evt.newValue)
{
field.SetValueWithoutNotify(clamped);
}
EditorPrefs.SetInt(EditorPrefKeys.BatchExecuteMaxCommands, clamped);
});
container.Add(field);
var hint = new Label($"(max {BatchExecute.AbsoluteMaxCommandsPerBatch})");
hint.style.marginLeft = 4;
hint.style.color = new UnityEngine.Color(0.5f, 0.5f, 0.5f);
hint.style.fontSize = 10;
container.Add(hint);
return container;
}
private void OnManageSceneScreenshotClicked()
{
try
{
var response = ManageScene.ExecuteScreenshot();
if (response is SuccessResponse success && !string.IsNullOrWhiteSpace(success.Message))
{
McpLog.Info(success.Message);
}
else if (response is ErrorResponse error && !string.IsNullOrWhiteSpace(error.Error))
{
McpLog.Error(error.Error);
}
else
{
McpLog.Info("Screenshot capture requested.");
}
}
catch (Exception ex)
{
McpLog.Error($"Failed to capture screenshot: {ex.Message}");
}
}
private void OnSceneViewScreenshotClicked()
{
try
{
var response = ManageScene.ExecuteSceneViewScreenshot();
if (response is SuccessResponse success && !string.IsNullOrWhiteSpace(success.Message))
{
McpLog.Info(success.Message);
}
else if (response is ErrorResponse error && !string.IsNullOrWhiteSpace(error.Error))
{
McpLog.Error(error.Error);
}
else
{
McpLog.Info("Scene View screenshot capture requested.");
}
}
catch (Exception ex)
{
McpLog.Error($"Failed to capture Scene View screenshot: {ex.Message}");
}
}
private void OnManageSceneMultiviewClicked()
{
try
{
var response = ManageScene.ExecuteMultiviewScreenshot();
if (response is SuccessResponse success)
{
// The data object is an anonymous type with imageBase64 — serialize to extract it
var json = Newtonsoft.Json.Linq.JObject.FromObject(success.Data);
string base64 = json["imageBase64"]?.ToString();
if (!string.IsNullOrEmpty(base64))
{
string folderSpec = MCPForUnity.Editor.Helpers.ScreenshotPreferences.Resolve(null);
string folder;
try
{
folder = MCPForUnity.Runtime.Helpers.ScreenshotUtility.ResolveFolderAbsolute(folderSpec);
}
catch (System.InvalidOperationException ex)
{
McpLog.Error(ex.Message);
return;
}
if (!System.IO.Directory.Exists(folder))
System.IO.Directory.CreateDirectory(folder);
string fileName = $"Multiview_{System.DateTime.Now:yyyyMMdd_HHmmss}.png";
string filePath = System.IO.Path.Combine(folder, fileName);
System.IO.File.WriteAllBytes(filePath, Convert.FromBase64String(base64));
// Import only when the file landed under Assets/.
string projectRoot = System.IO.Path.GetFullPath(System.IO.Path.Combine(UnityEngine.Application.dataPath, "..")).Replace('\\', '/');
string normalizedFile = filePath.Replace('\\', '/');
string normalizedRoot = projectRoot.EndsWith("/") ? projectRoot : projectRoot + "/";
string projectRelative = normalizedFile.StartsWith(normalizedRoot, System.StringComparison.OrdinalIgnoreCase)
? normalizedFile.Substring(normalizedRoot.Length)
: normalizedFile;
if (MCPForUnity.Runtime.Helpers.ScreenshotUtility.IsUnderAssets(projectRelative))
AssetDatabase.ImportAsset(projectRelative, ImportAssetOptions.ForceSynchronousImport);
McpLog.Info($"Multiview contact sheet saved to {projectRelative}");
}
else
{
McpLog.Info(success.Message ?? "Multiview capture completed.");
}
}
else if (response is ErrorResponse error && !string.IsNullOrWhiteSpace(error.Error))
{
McpLog.Error(error.Error);
}
}
catch (Exception ex)
{
McpLog.Error($"Failed to capture multiview: {ex.Message}");
}
}
private static Label CreateTag(string text)
{
var tag = new Label(text);
tag.AddToClassList("tool-tag");
return tag;
}
private static bool IsManageSceneTool(ToolMetadata tool) => string.Equals(tool?.Name, "manage_scene", StringComparison.OrdinalIgnoreCase);
private static bool IsManageCameraTool(ToolMetadata tool) => string.Equals(tool?.Name, "manage_camera", StringComparison.OrdinalIgnoreCase);
private static bool IsBatchExecuteTool(ToolMetadata tool) => string.Equals(tool?.Name, "batch_execute", StringComparison.OrdinalIgnoreCase);
private static bool IsBuiltIn(ToolMetadata tool) => tool?.IsBuiltIn ?? false;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c6b3eaf7efb642e89b9b9548458f72d6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,24 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<ui:VisualElement name="tools-section" class="section">
<ui:Label text="Tools" class="section-title" />
<ui:VisualElement class="section-content">
<ui:VisualElement class="setting-row" name="project-scoped-tools-row">
<ui:Label text="Project-Scoped Tools:" class="setting-label" />
<ui:Toggle name="project-scoped-tools-toggle" />
</ui:VisualElement>
<ui:Label name="tools-summary" class="help-text" text="Discovering tools..." />
<ui:VisualElement name="tools-actions" class="tool-actions">
<ui:VisualElement style="flex-direction: row;">
<ui:Button name="enable-all-button" text="Enable All" class="tool-action-button" />
<ui:Button name="disable-all-button" text="Disable All" class="tool-action-button" />
</ui:VisualElement>
<ui:VisualElement style="flex-direction: row;">
<ui:Button name="rescan-button" text="Rescan" class="tool-action-button" />
<ui:Button name="reconfigure-button" text="Reconfigure Clients" class="tool-action-button" />
</ui:VisualElement>
</ui:VisualElement>
<ui:Label name="tools-note" class="help-text" text="Changes apply after reconnecting or re-registering tools." />
<ui:VisualElement name="tool-category-container" class="tool-category-container" />
</ui:VisualElement>
</ui:VisualElement>
</ui:UXML>
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 5a94c7afd72c4dcf9f8a611d85c9a1e4
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}