#if UNITY_EDITOR using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; using OnDeviceAgent.Inference; namespace OnDeviceAgent.AgentCore.Editor { [CustomPropertyDrawer(typeof(VoiceStyleDropdownAttribute))] public sealed class VoiceStyleDropdownDrawer : PropertyDrawer { static readonly string[] DefaultStyles = { "F1", "F2", "F3", "F4", "F5", "M1", "M2", "M3", "M4", "M5" }; static readonly Dictionary s_Cache = new Dictionary(); static readonly Dictionary s_CacheAt = new Dictionary(); const double CacheSeconds = 5.0; public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { if (property.propertyType != SerializedPropertyType.String) { EditorGUI.PropertyField(position, property, label); return; } var attr = (VoiceStyleDropdownAttribute)attribute; var dir = ResolveDirectory(attr, property); var styles = GetStyles(dir); var current = property.stringValue ?? string.Empty; var options = new List(styles.Length + 1); if (!string.IsNullOrEmpty(current) && Array.IndexOf(styles, current) < 0) options.Add(current + " (custom)"); options.AddRange(styles); if (options.Count == 0) options.Add(string.IsNullOrEmpty(current) ? "(no styles)" : current); var selectedIndex = 0; for (var i = 0; i < options.Count; i++) { var name = options[i]; var marker = name.IndexOf(" (custom)", StringComparison.Ordinal); var bare = marker >= 0 ? name.Substring(0, marker) : name; if (bare == current) { selectedIndex = i; break; } } const float refreshWidth = 22f; var popupRect = new Rect(position.x, position.y, position.width - refreshWidth - 2f, position.height); var refreshRect = new Rect(position.xMax - refreshWidth, position.y, refreshWidth, position.height); EditorGUI.BeginChangeCheck(); var newIndex = EditorGUI.Popup(popupRect, label, selectedIndex, ToGuiContents(options)); if (EditorGUI.EndChangeCheck()) { var chosen = options[newIndex]; var marker = chosen.IndexOf(" (custom)", StringComparison.Ordinal); property.stringValue = marker >= 0 ? chosen.Substring(0, marker) : chosen; } if (GUI.Button(refreshRect, new GUIContent("↻", "Rescan " + dir))) { s_CacheAt[dir] = 0; GetStyles(dir); EditorWindow.focusedWindow?.Repaint(); } } static GUIContent[] ToGuiContents(List items) { var arr = new GUIContent[items.Count]; for (var i = 0; i < items.Count; i++) arr[i] = new GUIContent(items[i]); return arr; } static string ResolveDirectory(VoiceStyleDropdownAttribute attr, SerializedProperty property) { if (!string.IsNullOrWhiteSpace(attr.DirectoryField)) { var sibling = property.serializedObject.FindProperty(attr.DirectoryField); if (sibling != null && sibling.propertyType == SerializedPropertyType.String && !string.IsNullOrWhiteSpace(sibling.stringValue)) return sibling.stringValue.Trim(); } return string.IsNullOrWhiteSpace(attr.Directory) ? "Model/Supertonic/VoiceStyles" : attr.Directory.Trim(); } static string[] GetStyles(string directory) { if (s_Cache.TryGetValue(directory, out var cached) && s_CacheAt.TryGetValue(directory, out var at) && EditorApplication.timeSinceStartup - at < CacheSeconds) return cached; var result = DefaultStyles; try { var abs = ModelPathResolver.GetModelFilePath(directory); if (Directory.Exists(abs)) { var files = Directory.GetFiles(abs, "*.json"); if (files.Length > 0) { var names = new List(files.Length); for (var i = 0; i < files.Length; i++) names.Add(Path.GetFileNameWithoutExtension(files[i])); names.Sort(StringComparer.Ordinal); result = names.ToArray(); } } } catch { // Path resolution failed, fall through to defaults. } s_Cache[directory] = result; s_CacheAt[directory] = EditorApplication.timeSinceStartup; return result; } } } #endif