Initialer Upload neues Unity-Projekt

This commit is contained in:
Daniel Ocks
2025-07-21 09:11:14 +02:00
commit eeca72985b
14558 changed files with 1508140 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 36165b1ffe137194cb78c4bfa33954fb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ce0fdc4e4f6ccdd48abde94703ae1f18
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 69a95d9525bc5c14c8b3fb9cfe2af822
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using UnityEditor;
using UnityEngine;
namespace Meta.Voice.Hub.Content
{
public class Sample : ScriptableObject
{
[Header("Content")]
public string title;
[TextArea]
public string description;
public Texture2D tileImage;
public Texture2D screenshot;
[Header("Resource Paths")]
public SceneAsset sceneReference;
public string packageSampleName;
public string sampleSetId;
public float priority;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a0e3df96a2504a0085121315689deb30
timeCreated: 1683063947

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using Meta.Voice.Hub.Attributes;
using Meta.Voice.Hub.Interfaces;
using UnityEngine;
namespace Meta.Voice.Hub.Content
{
[MetaHubPageScriptableObject]
public class SamplesPage : ScriptableObject, IPageInfo
{
[SerializeField] private string _displayName;
[SerializeField] private string _prefix;
[SerializeField] private MetaHubContext _context;
[SerializeField] private int _priority = 0;
[SerializeField] private string _sampleSetId;
public string SampleSetId => _sampleSetId;
public string Name => _displayName ?? name;
public string Context => _context.Name;
public int Priority => _priority;
public string Prefix => _prefix;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c0e9b6410559489dbe69ca4c70a9ad70
timeCreated: 1683066838

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using Meta.Voice.Hub.Attributes;
using Meta.Voice.Hub.Interfaces;
using Meta.Voice.Hub.UIComponents;
using UnityEditor;
using UnityEngine;
using UnityEngine.Serialization;
namespace Meta.Voice.Hub
{
[MetaHubPageScriptableObject]
public class ImagePage : ScriptableObject, IPageInfo
{
[SerializeField] private string _displayName;
[SerializeField] private string _prefix;
[SerializeField] private MetaHubContext _context;
[SerializeField] private int _priority = 0;
[SerializeField] [FormerlySerializedAs("image")]
private Texture2D _image;
public string Name => _displayName ?? name;
public string Context => _context?.Name;
public int Priority => _priority;
public string Prefix => _prefix;
internal Texture2D Image => _image;
}
[CustomEditor(typeof(ImagePage))]
public class ImageDisplayScriptableObjectEditor : Editor
{
private ImagePage _imageDisplay;
private ImageView _imageView;
private void OnEnable()
{
_imageDisplay = (ImagePage)target;
_imageView = new ImageView(this);
}
public override void OnInspectorGUI()
{
if (_imageDisplay.Image)
{
_imageView.Draw(_imageDisplay.Image);
}
else
{
// Draw the default properties
base.OnInspectorGUI();
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a249c5c9df69431f935e9e49b478eab4
timeCreated: 1680673274

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 598c7a56ab9a46c7919d1cae7c0bfbc2
timeCreated: 1683064505

View File

@ -0,0 +1,317 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
using Sample = Meta.Voice.Hub.Content.Sample;
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
using PackageSample = UnityEditor.PackageManager.UI.Sample;
namespace Meta.Voice.Hub.Inspectors
{
[CustomEditor(typeof(Sample))]
public class SampleEditor : Editor
{
#if VSDK_INTERNAL
private bool edit = true;
#else
private bool edit = false;
#endif
private Sample _sample;
// Layout
private const float _sampleMargin = 5f;
private const float _sampleButtonHeight = 30f;
private const float _sampleIconMaxWidth = 540f;
private static GUIStyle _sampleTitleStyle;
private static GUIStyle _sampleWordwrapStyle;
private static GUIStyle _sampleSizeStyle;
// Text
private const string _textOpenSample = "Open Sample";
private const string _textImportSample = "Import Sample";
private const string _textInsideSample = "Currently Open";
private const string _textMissingSample = "Sample Not Found";
private void OnEnable()
{
_sample = (Sample) target;
}
public override void OnInspectorGUI()
{
DrawSample(_sample, EditorGUIUtility.currentViewWidth - 40f, true);
#if VSDK_INTERNAL
GUILayout.Space(16);
edit = EditorGUILayout.Foldout(edit, "Edit");
#endif
if (edit)
{
base.OnInspectorGUI();
}
}
/// <summary>
/// Draw method for a sample
/// </summary>
/// <param name="sample">Sample to be drawn</param>
/// <param name="tileSize">Width of a tile</param>
public static void DrawSample(Sample sample, float tileSize, bool showDescription = false)
{
// Generate all required styles
InitStyles();
// Begin
GUILayout.BeginVertical(EditorStyles.helpBox, GUILayout.Width(tileSize));
GUILayout.BeginHorizontal();
GUILayout.Space(_sampleMargin);
GUILayout.BeginVertical();
// Image
if (sample.tileImage != null)
{
float imageWidth = tileSize - _sampleMargin * 2f;
imageWidth = Mathf.Min(imageWidth, sample.tileImage.width, _sampleIconMaxWidth);
float imageHeight = imageWidth * sample.tileImage.height / sample.tileImage.width;
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.Label(sample.tileImage, GUILayout.Width(imageWidth), GUILayout.Height(imageHeight));
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
}
// Title
GUILayout.Label(sample.title, _sampleTitleStyle, GUILayout.Height(_sampleTitleStyle.lineHeight * 2));
GUILayout.Space(_sampleMargin);
// Description if applicable
if (showDescription)
{
GUILayout.Label(sample.description, _sampleWordwrapStyle);
GUILayout.Space(_sampleMargin);
}
// Open/Import button
DrawOpenSampleButton(sample);
GUILayout.Space(_sampleMargin);
// Complete
GUILayout.EndVertical();
GUILayout.Space(_sampleMargin);
GUILayout.EndHorizontal();
GUILayout.EndVertical();
}
// Draw button for open sample
public static void DrawOpenSampleButton(Sample sample)
{
// Get sample package if possible
PackageSample packagedSample = GetPackageSample(sample);
// Already opened
if (IsSampleOpened(sample))
{
bool defaultEnabled = GUI.enabled;
GUI.enabled = false;
GUILayout.Button(_textInsideSample, GUILayout.Height(_sampleButtonHeight));
GUI.enabled = defaultEnabled;
}
// Open
else if (CanOpenSample(sample, packagedSample))
{
// Show open button
if (GUILayout.Button(_textOpenSample, GUILayout.Height(_sampleButtonHeight)))
{
OpenSample(sample, packagedSample);
}
}
// Import
else if (IsPackageSampleValid(packagedSample) && !packagedSample.isImported)
{
// Show import button
GUILayout.BeginHorizontal();
if (GUILayout.Button(_textImportSample, GUILayout.Height(_sampleButtonHeight)))
{
ImportSample(packagedSample);
}
// Show import size
GUIContent sizeContent = new GUIContent(GetSampleSize(packagedSample));
float sizeWidth = _sampleSizeStyle.CalcSize(sizeContent).x;
GUILayout.Label(sizeContent, _sampleSizeStyle, GUILayout.Width(sizeWidth), GUILayout.Height(_sampleButtonHeight));
GUILayout.EndHorizontal();
}
// Error log
else
{
Color defaultColor = GUI.color;
bool defaultEnabled = GUI.enabled;
GUI.color = Color.red;
GUI.enabled = false;
GUILayout.Button(_textMissingSample, GUILayout.Height(_sampleButtonHeight));
GUI.color = defaultColor;
GUI.enabled = defaultEnabled;
}
}
private static void InitStyles()
{
if (null == _sampleTitleStyle)
{
_sampleTitleStyle = new GUIStyle(EditorStyles.boldLabel);
_sampleTitleStyle.wordWrap = true;
_sampleTitleStyle.fontSize = 16;
_sampleTitleStyle.alignment = TextAnchor.UpperLeft;
}
if (null == _sampleWordwrapStyle)
{
_sampleWordwrapStyle = new GUIStyle(EditorStyles.label);
_sampleWordwrapStyle.wordWrap = true;
}
if (null == _sampleSizeStyle)
{
_sampleSizeStyle = new GUIStyle(EditorStyles.label);
_sampleSizeStyle.alignment = TextAnchor.MiddleRight;
_sampleSizeStyle.fontSize = 12;
}
}
/// <summary>
/// Checks if a sample is currently opened
/// </summary>
/// <param name="sample">The sample asset data referencing the sample scene.</param>
public static bool IsSampleOpened(Sample sample)
{
if (sample.sceneReference != null)
{
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene scene = SceneManager.GetSceneAt(i);
if (scene.isLoaded && string.Equals(sample.sceneReference.name, scene.name))
{
return true;
}
}
}
return false;
}
/// <summary>
/// Checks if a sample can be opened, if false the sample must be imported.
/// </summary>
/// <param name="sample">The sample data to be checked</param>
/// <returns>True if sample is imported & can be opened</returns>
public static bool CanOpenSample(Sample sample) => CanOpenSample(sample, GetPackageSample(sample));
// Checks if sample scene reference can be opened or if a packaged sample is imported
private static bool CanOpenSample(Sample sample, PackageSample packagedSample)
{
return sample.sceneReference != null || (IsPackageSampleValid(packagedSample) && packagedSample.isImported);
}
/// <summary>
/// Opens a sample if possible
/// </summary>
/// <param name="sample">The sample data asset to be opened</param>
public static void OpenSample(Sample sample) => OpenSample(sample, GetPackageSample(sample));
// Opens a sample scene if found, otherwise attempts to find and open sample scene in package
private static void OpenSample(Sample sample, PackageSample packagedSample)
{
// Open via sample reference
if (sample.sceneReference != null)
{
string scenePath = AssetDatabase.GetAssetPath(sample.sceneReference);
EditorSceneManager.OpenScene(scenePath);
return;
}
// Failure
Debug.LogError($"HUB Sample - Cannot Open Sample\nSample: {sample.title}");
}
/// <summary>
/// Imports a sample into the current scene
/// </summary>
public static void ImportSample(PackageSample sample)
{
if (!sample.isImported)
{
sample.Import(PackageSample.ImportOptions.OverridePreviousImports);
}
}
// Returns package sample data struct if specified sample name matches the asset's package name & version
private static PackageSample GetPackageSample(Sample sample)
{
string sampleAssetPath = AssetDatabase.GetAssetPath(sample);
PackageInfo info = PackageInfo.FindForAssetPath(sampleAssetPath);
if (info != null && !string.IsNullOrEmpty(info.packageId))
{
foreach (var packageSample in PackageSample.FindByPackage(info.name, info.version))
{
if (string.Equals(packageSample.displayName, sample.packageSampleName))
{
return packageSample;
}
}
}
return new PackageSample();
}
// Returns true if the package sample struct is not empty
private static bool IsPackageSampleValid(PackageSample packagedSample)
{
return !string.IsNullOrEmpty(packagedSample.displayName);
}
/// <summary>
/// Determine size of a sample
/// </summary>
/// <param name="sample">The sample data asset to be opened</param>
public static void GetSampleSize(Sample sample) => GetSampleSize(GetPackageSample(sample));
// Gets sample size from from packaged sample
private static string GetSampleSize(PackageSample sample)
{
if (string.IsNullOrEmpty(sample.resolvedPath) || !Directory.Exists(sample.resolvedPath))
return "0 KB";
return ConvertToString(GetDirectorySize(sample.resolvedPath));
}
private static ulong GetDirectorySize(string directory)
{
ulong sizeInBytes = 0;
foreach (string fileName in Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories))
{
FileInfo fileInfo = new FileInfo(fileName);
sizeInBytes += (ulong) fileInfo.Length;
}
return sizeInBytes;
}
private static string ConvertToString(ulong sizeInBytes)
{
double size = sizeInBytes / _sizeUnitMax;
int index = 0;
while (size >= _sizeUnitMax && index < _sizeUnits.Length - 1)
{
++index;
size /= _sizeUnitMax;
}
return $"{size:0.##} {_sizeUnits[index]}";
}
private const double _sizeUnitMax = 1024.0;
private static readonly string[] _sizeUnits = new string[4]
{
"KB",
"MB",
"GB",
"TB"
};
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a932bbf699694d1b849d6375d1f3452b
timeCreated: 1683064516

View File

@ -0,0 +1,120 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using System.Collections.Generic;
using Meta.Voice.Hub.Content;
using Meta.Voice.Hub.Interfaces;
using Meta.Voice.Hub.Utilities;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
namespace Meta.Voice.Hub.Inspectors
{
[CustomEditor(typeof(SamplesPage))]
public class SamplesPageInspector : Editor, IOverrideSize
{
#if VSDK_INTERNAL
private bool edit = true;
#else
private bool edit = false;
#endif
const float _tileSize = 200;
private List<Sample> _samples = new List<Sample>();
private SamplesPage _samplePage;
public float OverrideWidth { get; set; } = -1;
public float OverrideHeight { get; set; } = -1;
private Vector2 _scrollOffset;
private float _minTileMargin = 15f;
private void OnEnable()
{
_samples.Clear();
_samplePage = (SamplesPage) target;
var samples = PageFinder.FindPages(typeof(Sample));
foreach (var so in samples)
{
var sample = (Sample)so;
if (sample.sampleSetId == _samplePage.SampleSetId)
{
_samples.Add(sample);
}
}
_samples.Sort((a, b) =>
{
int cmp = a.priority.CompareTo(b.priority);
if (cmp == 0) cmp = a.name.CompareTo(b.name);
return cmp;
});
}
public override void OnInspectorGUI()
{
_scrollOffset = GUILayout.BeginScrollView(_scrollOffset);
var windowWidth = (OverrideWidth > 0 ? OverrideWidth : EditorGUIUtility.currentViewWidth) - _minTileMargin;
var columns = (int) (windowWidth / (_tileSize + _minTileMargin));
int col = 0;
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
for (int i = 0; i < _samples.Count; i++)
{
if (col >= columns)
{
col = 0;
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
}
var sample = _samples[i];
SampleEditor.DrawSample(sample, _tileSize);
GUILayout.FlexibleSpace();
col++;
}
GUILayout.EndHorizontal();
#if VSDK_INTERNAL
GUILayout.Space(16);
edit = EditorGUILayout.Foldout(edit, "Edit");
#endif
if (edit)
{
base.OnInspectorGUI();
}
GUILayout.EndScrollView();
}
public static void OpenSceneFromGUID(string scenePath)
{
if (!string.IsNullOrEmpty(scenePath))
{
SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(scenePath);
if (sceneAsset != null)
{
if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
{
EditorSceneManager.OpenScene(scenePath);
}
}
else
{
Debug.LogError($"Scene asset not found at path: {scenePath}");
}
}
else
{
Debug.LogError($"Scene not found {scenePath}");
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d28e3c645082479ea5149b09828c745c
timeCreated: 1683067820

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8d6e8cb15dbf44a0b9fb35536ecd9d5f
timeCreated: 1680898102

View File

@ -0,0 +1,16 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
namespace Meta.Voice.Hub.Interfaces
{
public interface IOverrideSize
{
float OverrideWidth { get; set; }
float OverrideHeight { get; set; }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6ab13218e15145a792b14452a94dcf16
timeCreated: 1680898110

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 61bdbee982945e342b8e4e3766304221
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,235 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using System.Text.RegularExpressions;
using Meta.Voice.Hub.Interfaces;
using UnityEngine.Networking;
namespace Meta.Voice.Hub.Markdown
{
[CustomEditor(typeof(MarkdownPage))]
public class MarkdownInspector : Editor, IOverrideSize
{
private GUIStyle _linkStyle;
private GUIStyle _normalTextStyle;
private GUIStyle _imageLabelStyle;
private Dictionary<string, Texture2D> _cachedImages = new Dictionary<string, Texture2D>();
private Texture2D _badImageTex;
private Vector2 scrollView;
public float OverrideWidth { get; set; } = -1;
public float OverrideHeight { get; set; } = -1;
public override void OnInspectorGUI()
{
float padding = 55;
var markdownPage = ((MarkdownPage)target);
if (!markdownPage)
{
base.OnInspectorGUI();
return;
}
var markdownFile = markdownPage.MarkdownFile;
if (!markdownFile)
{
base.OnInspectorGUI();
return;
}
var text = markdownFile.text;
if (_linkStyle == null)
{
_linkStyle = new GUIStyle(GUI.skin.label)
{
richText = true,
wordWrap = true,
alignment = TextAnchor.MiddleLeft
};
}
if (_normalTextStyle == null)
{
_normalTextStyle = new GUIStyle(GUI.skin.label)
{
wordWrap = true,
richText = true,
alignment = TextAnchor.MiddleLeft
};
}
Event currentEvent = Event.current;
Regex urlRegex = new Regex(@"(https?://[^\s]+)");
Regex imageRegex = new Regex(@"!\[(.*?)\]\((.*?)\)");
Regex splitRegex = new Regex(@"(!\[.*?\]\(.*?\))|(https?://[^\s]+)");
string[] parts = splitRegex.Split(text);
scrollView = GUILayout.BeginScrollView(scrollView);
var windowWidth = (OverrideWidth > 0 ? OverrideWidth : EditorGUIUtility.currentViewWidth) - padding;
GUILayout.BeginVertical(GUILayout.Width(windowWidth));
foreach (string part in parts)
{
if (imageRegex.IsMatch(part))
{
Match imageMatch = imageRegex.Match(part);
if (imageMatch.Success)
{
string imagePath = imageMatch.Groups[2].Value;
if (!_cachedImages.ContainsKey(imagePath))
{
if (urlRegex.IsMatch(imagePath))
{
LoadImageFromUrl(imagePath);
}
else
{
var path = AssetDatabase.GetAssetPath(markdownPage);
var dir = Path.GetDirectoryName(path);
Texture2D image = AssetDatabase.LoadAssetAtPath<Texture2D>(dir + "/" + imagePath);
if (!image)
{
// Get the path of target markdown file
string markdownPath = AssetDatabase.GetAssetPath(markdownFile);
// Get the directory of the markdown file
string markdownDir = System.IO.Path.GetDirectoryName(markdownPath);
image = AssetDatabase.LoadAssetAtPath<Texture2D>(Path.Combine(markdownDir,
imagePath));
if (!image) image = _badImageTex;
}
_cachedImages[imagePath] = image;
}
}
if (_cachedImages.TryGetValue(imagePath, out Texture2D img) && img && img != _badImageTex)
{
float aspectRatio = 1;
float width = img.width;
float height = img.height;
if (img.width > windowWidth - padding)
{
width = windowWidth - padding;
aspectRatio = img.width / (float) img.height;
height = width / aspectRatio;
}
if (null == _imageLabelStyle)
{
_imageLabelStyle = new GUIStyle(GUI.skin.label)
{
alignment = TextAnchor.MiddleCenter,
imagePosition = ImagePosition.ImageAbove
};
}
GUIContent content = new GUIContent(img);
Rect imageLabelRect = GUILayoutUtility.GetRect(content, _imageLabelStyle,
GUILayout.Height(height), GUILayout.Width(width));
if (GUI.Button(imageLabelRect, content, _imageLabelStyle))
{
ImageViewer.ShowWindow(img, Path.GetFileNameWithoutExtension(imagePath));
}
}
}
}
else if (urlRegex.IsMatch(part))
{
EditorGUILayout.BeginHorizontal();
GUILayout.Space(EditorGUI.indentLevel * 15);
GUILayout.Label("<color=blue>" + part + "</color>", _linkStyle, GUILayout.MaxWidth(windowWidth));
Rect linkRect = GUILayoutUtility.GetLastRect();
if (currentEvent.type == EventType.MouseDown && linkRect.Contains(currentEvent.mousePosition))
{
Application.OpenURL(part);
}
EditorGUILayout.EndHorizontal();
}
else
{
EditorGUILayout.LabelField(ParseMarkdown(part), _normalTextStyle, GUILayout.MaxWidth(windowWidth));
}
}
GUILayout.EndVertical();
GUILayout.EndScrollView();
}
public static string ParseMarkdown(string markdown)
{
// Headers
markdown = Regex.Replace(markdown, @"^######\s(.*?)$", "<size=14><b>$1</b></size>", RegexOptions.Multiline);
markdown = Regex.Replace(markdown, @"^#####\s(.*?)$", "<size=16><b>$1</b></size>", RegexOptions.Multiline);
markdown = Regex.Replace(markdown, @"^####\s(.*?)$", "<size=18><b>$1</b></size>", RegexOptions.Multiline);
markdown = Regex.Replace(markdown, @"^###\s(.*?)$", "<size=20><b>$1</b></size>", RegexOptions.Multiline);
markdown = Regex.Replace(markdown, @"^##\s(.*?)$", "<size=22><b>$1</b></size>", RegexOptions.Multiline);
markdown = Regex.Replace(markdown, @"^#\s(.*?)$", "<size=24><b>$1</b></size>", RegexOptions.Multiline);
// Bold
markdown = Regex.Replace(markdown, @"\*\*(.*?)\*\*", "<b>$1</b>", RegexOptions.Multiline);
// Italic
markdown = Regex.Replace(markdown, @"\*(.*?)\*", "<i>$1</i>", RegexOptions.Multiline);
// Code blocks
markdown = Regex.Replace(markdown, @"(?s)```(.*?)```", m =>
{
var codeLines = m.Groups[1].Value.Trim().Split('\n');
string result = string.Empty;
foreach (var line in codeLines)
{
result += $" <color=#a1b56c>{line}</color>\n";
}
return result;
}, RegexOptions.Multiline);
// Raw Urls
markdown = Regex.Replace(markdown, @"(https?:\/\/[^\s""'<>]+)",
"<link><color=#a1b56c><u>$1</u></color></link>", RegexOptions.Multiline);
// Unordered lists
markdown = Regex.Replace(markdown, @"^\s*\*\s(.*?)$", "• $1", RegexOptions.Multiline);
// Ordered lists
markdown = Regex.Replace(markdown, @"^(\d+)\.\s(.*?)$", "$1. $2", RegexOptions.Multiline);
return markdown;
}
private void LoadImageFromUrl(string url)
{
_cachedImages[url] = _badImageTex;
UnityWebRequest request = UnityWebRequestTexture.GetTexture(url);
request.SendWebRequest().completed += operation =>
{
if (request.responseCode == 200)
{
Texture2D texture = DownloadHandlerTexture.GetContent(request);
_cachedImages[url] = texture;
Repaint();
}
else
{
Debug.LogError($"Failed to load image from URL [Error {request.responseCode}]: {url}");
}
};
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e1014ec949564436944663d476cf2c48
timeCreated: 1680838351

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using Meta.Voice.Hub;
using Meta.Voice.Hub.Attributes;
using Meta.Voice.Hub.Interfaces;
using UnityEngine;
namespace Meta.Voice.Hub.Markdown
{
[MetaHubPageScriptableObject]
public class MarkdownPage : ScriptableObject, IPageInfo
{
[SerializeField] private string _displayName;
[SerializeField] private string _prefix;
[SerializeField] private MetaHubContext _context;
[SerializeField] private TextAsset _markdownFile;
[SerializeField] private int _priority = 0;
internal TextAsset MarkdownFile => _markdownFile;
public string Name => _displayName ?? name;
public string Context => _context.Name;
public int Priority => _priority;
public string Prefix => _prefix;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 98bfdca232b84c3db2527e4b5c9d3b9d
timeCreated: 1680839218

View File

@ -0,0 +1,19 @@
{
"name": "Meta.Voice.Hub.Editor",
"rootNamespace": "Meta.Voice.Hub",
"references": [
"GUID:8e054d88729a3fb4bbe539499f824363",
"GUID:4c3436cc699cb25459568e9fa95b7fd7"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a4f6d7d3afe393a4c8034af2ef0f36a8
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,606 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Meta.Voice.Hub.Attributes;
using Meta.Voice.Hub.Interfaces;
using Meta.Voice.Hub.UIComponents;
using Meta.Voice.Hub.Utilities;
using Meta.Voice.TelemetryUtilities;
using UnityEditor;
using UnityEngine;
namespace Meta.Voice.Hub
{
public class MetaHub : EditorWindow
{
[SerializeField] private Texture2D _icon;
[SerializeField] private Texture2D _logoImage;
private int _leftPanelWidth = 200;
private List<MetaHubContext> _contexts = new List<MetaHubContext>();
private Dictionary<string, MetaHubContext> _contextMap = new Dictionary<string, MetaHubContext>();
private List<PageGroup> _pageGroups = new List<PageGroup>();
private Dictionary<MetaHubContext, PageGroup> _pageGroupMap = new Dictionary<MetaHubContext, PageGroup>();
public MetaHubContext PrimaryContext
{
get
{
if (ContextFilter.Count > 0)
{
var filter = ContextFilter.First();
if (_contextMap.TryGetValue(filter, out var context))
{
return context;
}
}
return _contexts[0];
}
}
public GUIContent TitleContent => new GUIContent(PrimaryContext.Title, PrimaryContext.Icon);
public Texture2D LogoImage => PrimaryContext.LogoImage ? PrimaryContext.LogoImage : _logoImage;
public const string DEFAULT_CONTEXT = "";
public virtual List<string> ContextFilter => new List<string>();
private string _selectedPageId;
public string SelectedPage
{
get
{
if (null == _selectedPageId)
{
_selectedPageId = SessionState.GetString(SessionKeySelPage, null);
}
return _selectedPageId;
}
set
{
if (_selectedPageId != value)
{
_selectedPageId = value;
Telemetry.LogInstantEvent(Telemetry.TelemetryEventId.OpenUi,
new Dictionary<Telemetry.AnnotationKey, string>()
{
{ Telemetry.AnnotationKey.PageId, _selectedPageId }
});
if (!string.IsNullOrEmpty(value))
{
SessionState.SetString(SessionKeySelPage, value);
}
}
}
}
private string SessionKeySelPage => GetType().Namespace + "." + GetType().Name + "::SelectedPage";
private PageGroup _rootPageGroup;
private class PageGroup
{
private MetaHubContext _context;
private List<PageReference> _pages = new List<PageReference>();
private HashSet<string> _addedPages = new HashSet<string>();
private FoldoutHierarchy<PageReference> _foldoutHierarchy = new FoldoutHierarchy<PageReference>();
private readonly Action<PageReference> _onDrawPage;
public MetaHubContext Context => _context;
public IEnumerable<PageReference> Pages => _pages;
public int PageCount => _pages.Count;
public FoldoutHierarchy<PageReference> Hierarchy => _foldoutHierarchy;
public PageGroup(MetaHubContext context, Action<PageReference> onDrawPage)
{
_context = context;
_onDrawPage = onDrawPage;
}
public void AddPage(PageReference page)
{
var pageId = page.PageId;
if (!_addedPages.Contains(pageId))
{
_addedPages.Add(pageId);
_pages.Add(page);
var prefix = page.info.Prefix?.Trim(new char[] { '/' });
if (prefix.Length > 0)
{
prefix += "/";
}
var path = "/" + prefix + page.info.Name;
_foldoutHierarchy.Add(path, new FoldoutHierarchyItem<PageReference> {
path = path,
item = page,
onDraw = _onDrawPage
});
}
}
public void Sort()
{
Sort(_pages);
}
public void Sort(List<PageReference> pages)
{
pages.Sort((a, b) =>
{
int compare = a.info.Priority.CompareTo(b.info.Priority);
if (compare == 0) compare = string.Compare(a.info.Name, b.info.Name);
return compare;
});
_foldoutHierarchy = new FoldoutHierarchy<PageReference>();
foreach (var page in _pages)
{
var path = "/" + page.info.Prefix + page.info.Name;
_foldoutHierarchy.Add(path, new FoldoutHierarchyItem<PageReference> {
path = path,
item = page,
onDraw = _onDrawPage
});
}
}
}
private struct PageReference
{
public IMetaHubPage page;
public IPageInfo info;
public string PageId => info.Context + "::" + info.Name;
}
private string _searchString = "";
private IMetaHubPage _selectedPage;
private Vector2 _scroll;
private Vector2 _leftScroll;
private IMetaHubPage ActivePage
{
get => _selectedPage;
set
{
if (_selectedPage != value)
{
if (null != _selectedPage)
{
DisableSelectedPage(_selectedPage);
}
_selectedPage = value;
EnableSelectedPage(_selectedPage);
}
}
}
private void InvokeLifecyle(IMetaHubPage page, string lifecycleMethod)
{
if (null == page) return;
var method = page.GetType()
.GetMethod(lifecycleMethod, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
method?.Invoke(page, new object[0]);
}
private void DisableSelectedPage(IMetaHubPage page)
{
InvokeLifecyle(page, "OnDisable");
}
private void EnableSelectedPage(IMetaHubPage page)
{
if (null == page) return;
var method = page.GetType()
.GetMethod("OnWindow", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
method?.Invoke(page, new object[] {this});
InvokeLifecyle(page, "OnEnable");
}
private void OnSelectionChange()
{
InvokeLifecyle(ActivePage, "OnSelectionChange");
}
protected virtual void OnEnable()
{
UpdateContextFilter();
minSize = new Vector2(400, 400);
}
private void OnDisable()
{
if (null != _selectedPage)
{
ActivePage = null;
}
}
protected void AddChildContexts(List<string> filter)
{
var parents = new HashSet<string>();
foreach (var parent in filter)
{
parents.Add(parent);
}
var contexts = ContextFinder.FindAllContextAssets<MetaHubContext>();
foreach (var context in contexts)
{
if (!context) continue;
if (null == context.ParentContexts) continue;
foreach (var parentContext in context.ParentContexts)
{
if(parents.Contains(parentContext)) filter.Add(context.Name);
}
}
}
public void UpdateContextFilter()
{
if(null == _rootPageGroup) _rootPageGroup = new PageGroup(null, DrawPageEntry);
_contexts = ContextFinder.FindAllContextAssets<MetaHubContext>();
_contexts.Sort((a, b) => a.Priority.CompareTo(b.Priority));
foreach (var context in _contexts)
{
_contextMap[context.Name] = context;
var pageGroup = new PageGroup(context, DrawPageEntry);
if (!_pageGroupMap.ContainsKey(context))
{
_pageGroups.Add(pageGroup);
_pageGroupMap[context] = pageGroup;
foreach (var soPage in context.ScriptableObjectReflectionPages)
{
var pages = PageFinder.FindPages(soPage.scriptableObjectType);
foreach (var so in pages)
{
var page = new ScriptableObjectPage(so, context.Name, prefix: soPage.namePrefix, priority: soPage.priorityModifier);
AddPage(new PageReference
{
page = page,
info = page
});
}
}
}
}
foreach (var page in ContextFinder.FindAllContextAssets<MetaHubPage>())
{
AddPage(new PageReference
{
page = page,
info = page
});
}
foreach (var pageType in PageFinder.FindPages())
{
var pageInfo = PageFinder.GetPageInfo(pageType);
if (pageInfo is MetaHubPageScriptableObjectAttribute)
{
var pages = PageFinder.FindPages(pageType);
foreach (var page in pages)
{
var soPage = new ScriptableObjectPage(page, pageInfo);
AddPage(new PageReference
{
page = soPage,
info = soPage
});
}
}
else
{
IMetaHubPage page;
if (pageType.IsSubclassOf(typeof(ScriptableObject)))
{
page = (IMetaHubPage) ScriptableObject.CreateInstance(pageType);
}
else
{
page = Activator.CreateInstance(pageType) as IMetaHubPage;
}
if(page is IPageInfo info) AddPage(new PageReference { page = page, info = info});
else AddPage(new PageReference { page = page, info = pageInfo});
}
}
// Sort the pages by priority then alpha
foreach (var group in _pageGroupMap.Values)
{
group.Sort();
}
}
private void AddPage(PageReference page)
{
if (string.IsNullOrEmpty(page.info.Context)) _rootPageGroup.AddPage(page);
else if (_contextMap.TryGetValue(page.info.Context, out var groupKey)
&& _pageGroupMap.TryGetValue(groupKey, out var group))
{
group.AddPage(page);
}
}
protected virtual void OnGUI()
{
titleContent = TitleContent;
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
_searchString = EditorGUILayout.TextField(_searchString, EditorStyles.toolbarSearchField);
GUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
DrawLeftPanel();
DrawRightPanel();
EditorGUILayout.EndHorizontal();
}
private void DrawLeftPanel()
{
EditorGUILayout.BeginVertical(GUILayout.Width(_leftPanelWidth));
var logo = LogoImage;
// Draw logo image
if (logo)
{
float aspectRatio = logo.width / (float) logo.height;
GUILayout.Box(logo, GUILayout.Width(_leftPanelWidth), GUILayout.Height(_leftPanelWidth / aspectRatio));
}
_leftScroll = GUILayout.BeginScrollView(_leftScroll);
DrawPageGroup(_rootPageGroup);
foreach (var context in _pageGroups)
{
DrawPageGroup(context);
}
GUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
private void DrawPageGroup(PageGroup group)
{
if (!IsGroupVisible(group)) return;
var searchMatchedGroupContext = ContextFilter.Count != 1 && IsGroupInSearch(group);
List<PageReference> pages = new List<PageReference>();
if (!string.IsNullOrEmpty(_searchString) && !searchMatchedGroupContext)
{
foreach (var page in group.Pages)
{
if (PageInSearch(page))
{
pages.Add(page);
}
}
}
if (ContextFilter.Count != 1 && (string.IsNullOrEmpty(_searchString) || pages.Count > 0))
{
if (group.Context != PrimaryContext && null != group.Context &&
(string.IsNullOrEmpty(_searchString) && group.PageCount > 0 || pages.Count > 0) &&
!string.IsNullOrEmpty(group.Context.Name) && group.Context.ShowPageGroupTitle)
{
GUILayout.Space(8);
GUILayout.Label(group.Context.Name, EditorStyles.boldLabel);
}
}
if(!string.IsNullOrEmpty(_searchString))
{
for (int i = 0; i < pages.Count; i++)
{
DrawPageEntry(pages[i]);
}
}
else
{
group.Hierarchy.Draw();
}
}
private bool PageInSearch(PageReference page)
{
#if UNITY_2021_1_OR_NEWER
return page.info.Name.Contains(_searchString, StringComparison.OrdinalIgnoreCase);
#else
return page.info.Name.ToLower().Contains(_searchString.ToLower());
#endif
}
private bool IsGroupInSearch(PageGroup group)
{
#if UNITY_2021_1_OR_NEWER
return group.Context && group.Context.Name.Contains(_searchString,
StringComparison.OrdinalIgnoreCase);
#else
return group.Context && group.Context.Name.ToLower().Contains(_searchString.ToLower());
#endif
}
private bool IsGroupVisible(PageGroup group)
{
return group.PageCount > 0 &&
ContextFilter.Count == 0 && (!group.Context || group.Context.AllowWithoutContextFilter) ||
ContextFilter.Contains(group.Context ? group.Context.Name : "");
}
private void DrawPageEntry(PageReference page)
{
GUIStyle optionStyle = new GUIStyle(GUI.skin.label);
optionStyle.normal.background = null;
optionStyle.normal.textColor = _selectedPage == page.page ? Color.white : GUI.skin.label.normal.textColor;
if (string.IsNullOrEmpty(SelectedPage))
{
// TODO: We will need to improve this logic.
if (!string.IsNullOrEmpty(SelectedPage) && page.PageId == SelectedPage) ActivePage = page.page;
else if(string.IsNullOrEmpty(SelectedPage)) ActivePage = page.page;
SelectedPage = page.PageId;
}
else if (null == ActivePage)
{
if (page.PageId == SelectedPage)
{
ActivePage = page.page;
}
}
EditorGUILayout.BeginHorizontal();
{
Rect optionRect = GUILayoutUtility.GetRect(GUIContent.none, optionStyle, GUILayout.ExpandWidth(true), GUILayout.Height(20));
bool isHover = optionRect.Contains(Event.current.mousePosition);
if (isHover)
{
EditorGUIUtility.AddCursorRect(optionRect, MouseCursor.Link);
}
Color backgroundColor;
if (page.page == _selectedPage)
{
backgroundColor = EditorGUIUtility.isProSkin ? new Color(0.22f, 0.44f, 0.88f) : new Color(0.24f, 0.48f, 0.90f);
}
else
{
backgroundColor = Color.clear;
}
EditorGUI.DrawRect(optionRect, backgroundColor);
GUI.Label(optionRect, new GUIContent(page.info.Name, page.info.Name), optionStyle);
if (Event.current.type == EventType.MouseDown && isHover)
{
ActivePage = page.page;
SelectedPage = page.PageId;
Event.current.Use();
}
}
EditorGUILayout.EndHorizontal();
}
protected virtual void DrawRightPanel()
{
// Create a GUIStyle with a darker background color
GUIStyle darkBackgroundStyle = new GUIStyle();
Texture2D backgroundTexture = new Texture2D(1, 1);
backgroundTexture.SetPixel(0, 0, new Color(0f, 0f, 0f, .25f));
backgroundTexture.Apply();
darkBackgroundStyle.normal.background = backgroundTexture;
// Apply the dark background style to the right panel
EditorGUILayout.BeginVertical(darkBackgroundStyle, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
if (_selectedPage is ScriptableObjectPage soPage)
{
if(soPage.Editor is IOverrideSize size && Event.current.type == EventType.Layout) {
size.OverrideWidth = EditorGUIUtility.currentViewWidth - _leftPanelWidth;
}
}
_selectedPage?.OnGUI();
EditorGUILayout.EndVertical();
}
public static T ShowWindow<T>(params string[] contexts) where T : MetaHub
{
var window = EditorWindow.GetWindow<T>();
window.ActivePage = null;
window.titleContent = new GUIContent("Meta Hub");
window.UpdateContextFilter();
window.Show();
return window;
}
}
internal class ScriptableObjectPage : IMetaHubPage, IPageInfo
{
private readonly ScriptableObject _page;
private string _context;
private Editor _editor;
private string _name;
private string _prefix = "";
private int _priority;
public string Name => _name;
public string Prefix => _prefix;
public string Context => _context;
public Editor Editor => _editor;
public int Priority => _priority;
public ScriptableObjectPage(ScriptableObject page, MetaHubPageAttribute pageInfo)
{
_page = page;
_context = pageInfo.Context;
_priority = pageInfo.Priority;
_prefix = pageInfo.Prefix;
UpdatePageInfo();
}
public ScriptableObjectPage(ScriptableObject page, string context, string prefix = "", int priority = 0)
{
_page = page;
_context = context;
_priority = priority;
_prefix = prefix;
UpdatePageInfo();
}
private void UpdatePageInfo()
{
if (_page is IPageInfo info)
{
if (!string.IsNullOrEmpty(info.Name)) _name = info.Name;
if (!string.IsNullOrEmpty(info.Context)) _context = info.Context;
if (!string.IsNullOrEmpty(info.Prefix)) _prefix = info.Prefix;
if (info.Priority != 0) _priority = info.Priority;
}
else
{
_name = _page.name;
}
}
public void OnGUI()
{
if (_page)
{
// Create an editor for the assigned ScriptableObject
if (_editor == null || _editor.target != _page)
{
_editor = Editor.CreateEditor(_page);
}
// Render the ScriptableObject with its default editor
_editor.OnInspectorGUI();
}
}
}
}

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 271f6b1cb92788340a1dae6d90354a0c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- m_ViewDataDictionary: {instanceID: 0}
- _icon: {fileID: 2800000, guid: 439d6ccc4069b824cae61eb7ac48e64d, type: 3}
- _logoImage: {fileID: 2800000, guid: d42013762536e8042a5211ecbc89b7f9, type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using System;
using System.Drawing;
using UnityEngine;
namespace Meta.Voice.Hub
{
public class MetaHubContext : ScriptableObject
{
[Header("Context Configuration")]
[Tooltip("The title of the window if this is the primary context")]
[SerializeField] private string _windowTitle;
[Tooltip("The logo to use if this is the primary context")]
[SerializeField] private Texture2D _logo;
[Tooltip("The icon to use if this is the primary context")]
[SerializeField] private Texture2D _icon;
[Tooltip("The priority of this context. Lower number means higher probability that this will be the primary context.")]
[SerializeField] private int _priority = 1000;
[Tooltip("If there are no context filters you will have")]
[SerializeField] private bool _allowWithoutContextFilter = true;
[Header("Page Content")]
[SerializeField] private bool showPageGroupTitle = true;
[SerializeField] private MetaHubPage[] _pages;
[SerializeField] private ScriptableObjectReflectionPage[] _scriptableObjectPages;
[SerializeField] private string _defaultPage;
[SerializeField] private string[] _parentContexts;
public virtual string Name => name;
public virtual int Priority => _priority;
public virtual Texture2D LogoImage => _logo;
public virtual Texture2D Icon => _icon;
public virtual string DefaultPage => _defaultPage;
public virtual string Title => _windowTitle;
public virtual bool ShowPageGroupTitle => showPageGroupTitle;
public virtual bool AllowWithoutContextFilter => _allowWithoutContextFilter;
public virtual string[] ParentContexts => _parentContexts;
public virtual ScriptableObjectReflectionPage[] ScriptableObjectReflectionPages => _scriptableObjectPages;
[Serializable]
public class ScriptableObjectReflectionPage
{
[SerializeField] public string scriptableObjectType;
[SerializeField] public string namePrefix;
[Tooltip("A modifier the priority of all pages of this type.")]
[SerializeField] public int priorityModifier;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1a05db9a1dc84904b336c0c789666114
timeCreated: 1680655384

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using Meta.Voice.Hub.Interfaces;
using UnityEngine;
namespace Meta.Voice.Hub
{
public class MetaHubPage : ScriptableObject, IMetaHubPage, IPageInfo
{
/// <summary>
/// The context this page will fall under
/// </summary>
[SerializeField] private string _context;
/// <summary>
/// A prefix that will show up before the name of the page. This is a good place to insert page hierarchy etc.
/// </summary>
[SerializeField] private string _prefix;
/// <summary>
/// The sorting priority of the page
/// </summary>
[SerializeField] private int _priority;
public virtual string Name => name;
public virtual string Context => _context;
public virtual int Priority => _priority;
public virtual string Prefix => _context;
public virtual void OnGUI()
{
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 731e6f73e53947cba9d7d0c6caf7d0a8
timeCreated: 1680655880

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eedddfe631e0492d931ec56bb2d2eec4
timeCreated: 1680888150

View File

@ -0,0 +1,135 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Eventing.Reader;
using UnityEditor;
using UnityEngine;
namespace Meta.Voice.Hub.UIComponents
{
public class FoldoutHierarchy<T>
{
private Dictionary<string, FoldoutGroup<T>> _groups = new Dictionary<string, FoldoutGroup<T>>();
private List<FoldoutGroup<T>> _orderedGroups = new List<FoldoutGroup<T>>();
public void Add(string path, FoldoutHierarchyItem<T> item)
{
string[] parts = path.Split('/');
FoldoutGroup<T> currentGroup = null;
for (int i = 0; i < parts.Length; i++)
{
string key = string.Join("/", parts, 0, i + 1);
if (!_groups.ContainsKey(key))
{
FoldoutGroup<T> newGroup = new FoldoutGroup<T>(parts[i]);
_groups.Add(key, newGroup);
_orderedGroups.Add(newGroup);
if (currentGroup != null)
{
currentGroup.AddChild(newGroup, item, i == parts.Length - 1);
}
}
currentGroup = _groups[key];
}
}
public void Draw()
{
foreach (var group in _orderedGroups)
{
if (group.Parent == null)
{
group.Draw();
}
}
}
}
public class FoldoutHierarchyItem<T>
{
public string path;
public T item;
public Action<T> onDraw;
}
public class FoldoutGroup<T>
{
private string _name;
private FoldoutGroup<T> _parent;
private List<object> _children = new List<object>();
private List<FoldoutHierarchyItem<T>> _data = new List<FoldoutHierarchyItem<T>>();
private int _indentSpace = 10;
private bool _isFoldedOut = false;
public string Name => _name;
public FoldoutGroup<T> Parent => _parent;
public FoldoutGroup(string name)
{
this._name = name;
}
public void AddChild(FoldoutGroup<T> child, FoldoutHierarchyItem<T> data, bool isLeaf)
{
child._parent = this;
_data.Add(data);
if(isLeaf) _children.Add(data);
else _children.Add(child);
}
public void Draw(int indentLevel = 0)
{
if (string.IsNullOrEmpty(_name))
{
DrawExpanded(indentLevel);
}
else
{
GUILayout.BeginHorizontal();
if (indentLevel >= 0)
{
GUILayout.Space(_indentSpace);
}
GUILayout.BeginVertical();
_isFoldedOut = EditorGUILayout.Foldout(_isFoldedOut, _name, true);
if (_isFoldedOut)
{
DrawExpanded(indentLevel);
}
GUILayout.EndVertical();
GUILayout.EndHorizontal();
}
}
private void DrawExpanded(int indentLevel)
{
foreach (var child in _children)
{
if (child is FoldoutGroup<T> foldoutGroup)
{
foldoutGroup.Draw(indentLevel);
} else if (child is FoldoutHierarchyItem<T> leaf)
{
GUILayout.BeginHorizontal();
if (indentLevel >= 0)
{
GUILayout.Space(_indentSpace);
}
GUILayout.BeginVertical();
leaf.onDraw(leaf.item);
GUILayout.EndVertical();
GUILayout.EndHorizontal();
}
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5b3b26502f774dd2b597ed1657a8f0a4
timeCreated: 1681274028

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using System;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Meta.Voice.Hub.UIComponents
{
public class ImageView
{
private EditorWindow _editorWindow;
private Editor _editor;
private Vector2 pan;
private float zoom = -1f;
public ImageView(EditorWindow editorWindow) => _editorWindow = editorWindow;
public ImageView(Editor editor) => _editor = editor;
private float ViewHeight => _editorWindow ? _editorWindow.position.height : Screen.height;
private float ViewWidth => _editorWindow ? _editorWindow.position.width : EditorGUIUtility.currentViewWidth;
private void Repaint()
{
if(_editorWindow) _editorWindow.Repaint();
else if (_editor) _editor.Repaint();
}
public void Draw(Texture2D image)
{
GUILayout.Box("",GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
var windowRect = GUILayoutUtility.GetLastRect();
if (windowRect.width <= 1 && windowRect.height <= 1) return;
if (image == null)
{
EditorGUILayout.HelpBox("No Texture2D assigned.", MessageType.Info);
return;
}
// Handle input for panning and zooming
HandleInput();
GUI.BeginGroup(windowRect);
var imageWidth = image.width * zoom;
var imageHeight = image.height * zoom;
if (zoom < 0 || imageWidth < windowRect.width && imageHeight < windowRect.height)
{
float widthScale = windowRect.width / image.width;
float heightScale = windowRect.height / image.height;
zoom = Mathf.Min(widthScale, heightScale);
}
if (imageWidth < windowRect.width) pan.x = (windowRect.width - imageWidth) / 2.0f;
else if (pan.x + imageWidth < windowRect.width) pan.x += windowRect.width - (pan.x + imageWidth);
if (imageHeight < windowRect.height) pan.y = (windowRect.height - imageHeight) / 2.0f;
else if (pan.y + imageHeight < windowRect.height) pan.y += windowRect.height - (pan.y + imageHeight);
if (pan.x > 0) pan.x = 0;
if (pan.y > 0) pan.y = 0;
if (imageHeight < windowRect.height) pan.y = (windowRect.height - imageHeight) / 2.0f;
GUI.DrawTexture(new Rect(pan.x, pan.y, image.width * zoom, image.height * zoom), image, ScaleMode.ScaleAndCrop);
GUI.EndGroup();
}
private void HandleInput()
{
Event e = Event.current;
// Panning
if (e.type == EventType.MouseDown)
{
e.Use();
}
if (e.type == EventType.MouseDrag)
{
pan += e.delta;
e.Use();
}
// Zooming
if (e.type == EventType.ScrollWheel)
{
float zoomDelta = -e.delta.y * 0.01f;
zoom = Mathf.Clamp(zoom + zoomDelta, 0.1f, 10f);
e.Use();
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1767e34a605743579a6cb935eff19e64
timeCreated: 1680888171

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d10f6297f38d44b3b93a5b848ab5da32
timeCreated: 1680655605

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Meta.Voice.Hub.Utilities
{
public static class ContextFinder
{
public static List<T> FindAllContextAssets<T>() where T: ScriptableObject
{
string[] guids = AssetDatabase.FindAssets("t:" + typeof(T).Name);
List<T> assets = new List<T>();
foreach (string guid in guids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
T asset = AssetDatabase.LoadAssetAtPath<T>(assetPath);
if (asset != null)
{
if (!assets.Contains(asset))
{
assets.Add(asset);
}
}
}
return assets;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c235113b0b6c484a8f361e84786d7467
timeCreated: 1680655637

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using System;
using System.Collections.Generic;
using Meta.Voice.Hub.Attributes;
using UnityEditor;
using UnityEngine;
namespace Meta.Voice.Hub.Utilities
{
internal static class PageFinder
{
private static List<Type> _pages;
internal static List<Type> FindPages()
{
if (null == _pages)
{
_pages = ReflectionUtils.GetTypesWithAttribute<MetaHubPageAttribute>();
}
return _pages;
}
internal static MetaHubPageAttribute GetPageInfo(Type type)
{
var attributes = type.GetCustomAttributes(typeof(MetaHubPageAttribute), false);
return attributes.Length > 0 ? (MetaHubPageAttribute) attributes[0] : null;
}
internal static List<ScriptableObject> FindPages(Type t)
{
if (!typeof(ScriptableObject).IsAssignableFrom(t))
{
throw new ArgumentException("The specified type must be a ScriptableObject.");
}
return FindPages(t.Name);
}
public static List<ScriptableObject> FindPages(string type)
{
List<ScriptableObject> pages = new List<ScriptableObject>();
string[] guids = AssetDatabase.FindAssets($"t:{type}");
foreach (string guid in guids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
ScriptableObject asset = AssetDatabase.LoadAssetAtPath<ScriptableObject>(assetPath);
if (asset != null)
{
pages.Add(asset);
}
}
return pages;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b5b71dcbc6c2475a831e119b0d8701cf
timeCreated: 1680655836

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using System;
using System.Collections.Generic;
using System.Linq;
namespace Meta.Voice.Hub.Utilities
{
internal static class ReflectionUtils
{
private const string NAMESPACE_PREFIX = "Meta";
private static bool IsValidNamespace(Type type) =>
type.Namespace != null && type.Namespace.StartsWith(NAMESPACE_PREFIX);
private static List<Type> GetTypes<T>(Func<Type, bool> isValid)
{
return AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly =>
{
try
{
return assembly.GetTypes();
}
catch
{
return new Type[]{};
}
})
.Where(IsValidNamespace)
.Where(isValid)
.ToList();
}
internal static List<Type> GetTypesWithAttribute<T>() where T : Attribute
{
var attributeType = typeof(T);
return GetTypes<T>(type => type.GetCustomAttributes(attributeType, false).Length > 0);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c9dfa99b5c354fc9b6cd8f560ca1ff02
timeCreated: 1680790475

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 122d847b98c549f696fcfa52128264bb
timeCreated: 1680889697

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using Meta.Voice.Hub.UIComponents;
using UnityEngine;
using UnityEditor;
public class ImageViewer : EditorWindow
{
private Texture2D _image;
private ImageView _imageView;
public static void ShowWindow(Texture2D image, string title)
{
ImageViewer window = CreateInstance<ImageViewer>();
window._image = image;
window.titleContent = new GUIContent(title);
window.Show();
}
private void OnEnable()
{
if (_image == null)
{
Close();
return;
}
}
private void OnGUI()
{
if(null == _imageView) _imageView = new ImageView(this);
_imageView.Draw(_image);
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e775846ae33248d98afc58bedcbd6c12
timeCreated: 1680847262

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 61f50677da1080242ace79cb69dc6bdb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 259e970f8c624d50adde3e97c28d4db2
timeCreated: 1680653496

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using System;
namespace Meta.Voice.Hub.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public class MetaHubContextAttribute : Attribute
{
public string Context { get; private set; }
public int Priority { get; private set; }
public string LogoPath { get; private set; }
public MetaHubContextAttribute(string context, int priority = 1000, string pathToLogo = "")
{
Context = context;
Priority = priority;
LogoPath = pathToLogo;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: adaa391766724cfba11a9c2223e738b0
timeCreated: 1680653506

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
using System;
using Meta.Voice.Hub.Interfaces;
namespace Meta.Voice.Hub.Attributes
{
public class MetaHubPageAttribute : Attribute, IPageInfo
{
public string Name { get; private set; }
public string Context { get; private set; }
public int Priority { get; private set; }
public string Prefix { get; private set; }
public MetaHubPageAttribute(string name = null, string context = "", string prefix = "", int priority = 0)
{
Name = name;
Context = context;
Priority = priority;
Prefix = prefix;
}
}
public class MetaHubPageScriptableObjectAttribute : MetaHubPageAttribute
{
public MetaHubPageScriptableObjectAttribute(string context = "") : base(context: context)
{
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b7abd7a0ff8e45bc9cad665bffea5fae
timeCreated: 1680653617

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fa9a1019478641b2a667674cd4a94eb8
timeCreated: 1680672606

View File

@ -0,0 +1,15 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
namespace Meta.Voice.Hub.Interfaces
{
public interface IMetaHubPage
{
void OnGUI();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 816e6d4cdf174cf1981d648ac98b115c
timeCreated: 1680672614

View File

@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
namespace Meta.Voice.Hub.Interfaces
{
public interface IPageInfo
{
string Name { get; }
string Context { get; }
int Priority { get; }
string Prefix { get; }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 33e5585f3c294fddbdf1f2c3b8e6786b
timeCreated: 1680672632

View File

@ -0,0 +1,14 @@
{
"name": "Meta.Voice.Hub.Runtime",
"rootNamespace": "Meta.Voice.Hub",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8e054d88729a3fb4bbe539499f824363
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: