Storyline+Dialog初步

This commit is contained in:
SoulliesOfficial
2026-07-05 16:08:23 -04:00
parent afa8a56e1d
commit d031afd075
464 changed files with 25716 additions and 4209 deletions

View File

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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ba1664ec1a0467641a742eaadae146d4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6384593779a7421881db64cdb25db4c5
timeCreated: 1718984862

View File

@@ -0,0 +1,30 @@
//-----------------------------------------------------------------------
// <copyright file="OdinAddressableReflection.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
#if UNITY_EDITOR
#if !SIRENIX_INTERNAL
#pragma warning disable
#endif
using System.Reflection;
using UnityEditor.AddressableAssets.Settings;
namespace Sirenix.OdinInspector.Modules.Addressables.Editor.Internal
{
internal static class OdinAddressableReflection
{
public static FieldInfo AddressableAssetEntry_mGUID_Field;
static OdinAddressableReflection()
{
AddressableAssetEntry_mGUID_Field = typeof(AddressableAssetEntry).GetField("m_GUID", BindingFlags.Instance | BindingFlags.NonPublic);
}
internal static void EnsureConstructed() { }
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c1a8e9ae60bf4edebd0ec6052eb9064e
timeCreated: 1718984883

View File

@@ -0,0 +1,46 @@
//-----------------------------------------------------------------------
// <copyright file="OdinAddressableReflectionValidator.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
#if UNITY_EDITOR
#if SIRENIX_INTERNAL
using System.Collections;
using System.Reflection;
using Sirenix.OdinInspector.Editor.Validation;
using Sirenix.OdinInspector.Modules.Addressables.Editor.Internal;
[assembly: RegisterValidator(typeof(OdinAddressableReflectionValidator))]
namespace Sirenix.OdinInspector.Modules.Addressables.Editor.Internal
{
public class OdinAddressableReflectionValidator : GlobalValidator
{
public override IEnumerable RunValidation(ValidationResult result)
{
OdinAddressableReflection.EnsureConstructed();
FieldInfo[] fields = typeof(OdinAddressableReflection).GetFields(BindingFlags.Static | BindingFlags.Public);
for (var i = 0; i < fields.Length; i++)
{
if (fields[i].IsLiteral)
{
continue;
}
if (fields[i].GetValue(null) != null)
{
continue;
}
result.AddError($"[Odin Addressable Module]: {fields[i].Name} was not found.");
}
return null;
}
}
}
#endif
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 97bf306f5a4249b192921db9152934f6
timeCreated: 1718984873

View File

@@ -0,0 +1,29 @@
{
"name": "Sirenix.OdinInspector.Modules.Unity.Addressables",
"references": [
"Unity.Addressables",
"Unity.Addressables.Editor",
"Sirenix.Serialization",
"Sirenix.OdinInspector.Attributes",
"Sirenix.OdinInspector.Editor",
"Sirenix.Utilities.Editor",
"Sirenix.Utilities",
"Sirenix.OdinValidator.Editor"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [
"Sirenix.Serialization.dll",
"Sirenix.OdinInspector.Attributes.dll",
"Sirenix.OdinInspector.Editor.dll",
"Sirenix.Utilities.Editor.dll",
"Sirenix.Utilities.dll",
"Sirenix.OdinValidator.Editor.dll"
],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

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

View File

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

View File

@@ -0,0 +1,98 @@
//-----------------------------------------------------------------------
// <copyright file="AssetLabelReferenceValidator.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
#if UNITY_EDITOR
#if !SIRENIX_INTERNAL
#pragma warning disable
#endif
using UnityEngine;
using UnityEditor.AddressableAssets;
using Sirenix.OdinInspector.Editor.Validation;
using UnityEngine.AddressableAssets;
using Sirenix.OdinInspector.Modules.Addressables.Editor;
#if ODIN_VALIDATOR_3_1
[assembly: RegisterValidationRule(typeof(AssetLabelReferenceValidator), Description =
"This validator ensures that AssetLabelReferences marked with the Required attribute display an error " +
"message if they are not set. It can also be configured to require that all AssetLabelReferences be set " +
"by default; the Optional attribute can then be used to exclude specific AssetLabelReferences from " +
"validation.")]
#else
[assembly: RegisterValidator(typeof(AssetLabelReferenceValidator))]
#endif
namespace Sirenix.OdinInspector.Modules.Addressables.Editor
{
/// <summary>
/// Validator for AssetLabelReference values.
/// </summary>
public class AssetLabelReferenceValidator : ValueValidator<AssetLabelReference>
{
[Tooltip("If enabled, the validator will display an error message if the AssetLabelReference is not set. " +
"If disabled, the validator will only display an error message if the AssetLabelReference is set, but the " +
"assigned label does not exist.")]
[ToggleLeft]
public bool RequiredByDefault;
private bool required;
private bool optional;
private string requiredMessage;
protected override void Initialize()
{
var requiredAttr = this.Property.GetAttribute<RequiredAttribute>();
this.requiredMessage = requiredAttr?.ErrorMessage ?? $"<b>{this.Property.NiceName}</b> is required.";
if (this.RequiredByDefault)
{
required = true;
optional = Property.GetAttribute<OptionalAttribute>() != null;
}
else
{
required = requiredAttr != null;
optional = false;
}
}
protected override void Validate(ValidationResult result)
{
// If the Addressables settings have not been created, nothing else is really valid.
if (AddressableAssetSettingsDefaultObject.SettingsExists == false)
{
result.AddError("Addressables Settings have not been created.")
.WithButton("Open Settings Window", () => OdinAddressableUtility.OpenGroupsWindow());
return;
}
var value = Value?.labelString;
if (string.IsNullOrEmpty(value))
{
if (optional == false && required) // Optional == false & required? Nice.
{
result.AddError(requiredMessage).EnableRichText();
}
}
else
{
var labels = AddressableAssetSettingsDefaultObject.Settings.GetLabels();
if (labels.Contains(value) == false)
{
result.AddError($"Label <i>{value}</i> has not been created as a label.")
.WithButton("Open Label Settings", () => OdinAddressableUtility.OpenLabelsWindow());
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fcaf7dc3b9a98a545b301a1ea175055b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,284 @@
//-----------------------------------------------------------------------
// <copyright file="AssetReferenceValidator.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
#if UNITY_EDITOR
#if !SIRENIX_INTERNAL
#pragma warning disable
#endif
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEditor.AddressableAssets;
using Sirenix.OdinInspector.Editor.Validation;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine.AddressableAssets;
using Sirenix.OdinInspector.Modules.Addressables.Editor;
#if ODIN_VALIDATOR_3_1
[assembly: RegisterValidationRule(typeof(AssetReferenceValidator), Description =
"This validator provides robust integrity checks for your asset references within Unity. " +
"It validates whether an asset reference has been assigned, and if it's missing, raises an error. " +
"It further checks the existence of the main asset at the assigned path, ensuring it hasn't been " +
"inadvertently deleted or moved. The validator also verifies if the assigned asset is addressable " +
"and, if not, offers a fix to make it addressable. Moreover, it ensures the asset adheres to " +
"specific label restrictions set through the AssetReferenceUILabelRestriction attribute. " +
"Lastly, it performs checks on any sub-object linked to the asset, making sure it hasn't gone missing. " +
"This comprehensive validation system prevents hard-to-spot bugs and errors, " +
"fostering a more robust and efficient development workflow.")]
#else
[assembly: RegisterValidator(typeof(AssetReferenceValidator))]
#endif
namespace Sirenix.OdinInspector.Modules.Addressables.Editor
{
public class AssetReferenceValidator : ValueValidator<AssetReference>
{
[Tooltip("If true and the AssetReference is not marked with the Optional attribute, " +
"the validator will display an error message if the AssetReference is not set. " +
"If false, the validator will only display an error message if the AssetReference is set, " +
"but the assigned asset does not exist.")]
[ToggleLeft]
public bool RequiredByDefault;
private bool required;
private bool optional;
private string requiredMessage;
private List<AssetReferenceUIRestriction> restrictions;
protected override void Initialize()
{
var requiredAttr = this.Property.GetAttribute<RequiredAttribute>();
this.requiredMessage = requiredAttr?.ErrorMessage ?? $"<b>{this.Property.NiceName}</b> is required.";
if (this.RequiredByDefault)
{
this.required = true;
this.optional = this.Property.GetAttribute<OptionalAttribute>() != null;
}
else
{
this.required = requiredAttr != null;
this.optional = false;
}
this.restrictions = new List<AssetReferenceUIRestriction>();
foreach (var attr in this.Property.Attributes)
{
if (attr is AssetReferenceUIRestriction r)
{
this.restrictions.Add(r);
}
}
}
protected override void Validate(ValidationResult result)
{
// If the Addressables settings have not been created, nothing else is really valid.
if (AddressableAssetSettingsDefaultObject.SettingsExists == false)
{
result.AddError("Addressables Settings have not been created.")
.WithButton("Open Settings Window", () => OdinAddressableUtility.OpenGroupsWindow());
return;
}
var assetReference = this.Value;
var assetReferenceHasBeenAssigned = !string.IsNullOrEmpty(assetReference?.AssetGUID);
// No item has been assigned.
if (!assetReferenceHasBeenAssigned)
{
if (optional == false && required) // Optional == false & required? Nice.
{
result.AddError(this.requiredMessage).EnableRichText();
}
return;
}
var assetPath = AssetDatabase.GUIDToAssetPath(assetReference.AssetGUID);
var mainAsset = AssetDatabase.LoadMainAssetAtPath(assetPath);
// The item has been assigned, but is now missing.
if (mainAsset == null)
{
result.AddError($"The previously assigned main asset with path <b>'{assetPath}'</b> is missing. GUID <b>'{assetReference.AssetGUID}'</b>");
return;
}
var addressableAssetEntry = AddressableAssetSettingsDefaultObject.Settings.FindAssetEntry(assetReference.AssetGUID, true);
var isAddressable = addressableAssetEntry != null;
// Somehow an item sneaked through all of unity's validation measures and ended up not being addressable
// while still ending up in the asset reference object field.
if (!isAddressable)
{
result.AddError("Assigned item is not addressable.")
.WithFix<MakeAddressableFixArgs>("Make Addressable", args => OdinAddressableUtility.MakeAddressable(mainAsset, args.Group));
}
// Check the assigned item against any and all label restrictions.
else
{
if (OdinAddressableUtility.ValidateAssetReferenceRestrictions(restrictions, mainAsset, out var failedRestriction) == false)
{
if (failedRestriction is AssetReferenceUILabelRestriction labelRestriction)
{
result.AddError($"Asset reference is restricted to items with these specific labels <b>'{string.Join(", ", labelRestriction.m_AllowedLabels)}'</b>. The currently assigned item has none of them.")
.WithFix<AddLabelsFixArgs>("Add Labels", args => SetLabels(mainAsset, args.AssetLabels));
}
else
{
result.AddError("Restriction failed: " + failedRestriction.ToString());
}
}
}
// The assigned item had a sub object, but it's missing.
if (!string.IsNullOrEmpty(assetReference.SubObjectName))
{
var subObjects = OdinAddressableUtility.EnumerateAllActualAndVirtualSubAssets(mainAsset, assetPath);
var hasMissingSubObject = true;
foreach (var subObject in subObjects)
{
if (subObject.name == assetReference.SubObjectName)
{
hasMissingSubObject = false;
break;
}
}
if (hasMissingSubObject)
{
result.AddError($"The previously assigned sub asset with name <b>'{assetReference.SubObjectName}'</b> is missing.").EnableRichText();
}
}
if (assetReference.ValidateAsset(mainAsset) || assetReference.ValidateAsset(assetPath))
return;
if (assetReference is AssetReferenceSprite && assetReference.editorAsset is Sprite)
return;
result.AddError($"{assetReference.GetType().GetNiceFullName()}.ValidateAsset failed to validate assigned asset.");
}
private static void SetLabels(UnityEngine.Object obj, List<AssetLabel> assetLabels)
{
if (!AddressableAssetSettingsDefaultObject.SettingsExists) return;
var settings = AddressableAssetSettingsDefaultObject.Settings;
var guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(obj));
var entry = settings.FindAssetEntry(guid, false);
foreach (var assetLabel in assetLabels.Where(a => a.Toggled))
{
entry.SetLabel(assetLabel.Label, true, false, false);
}
settings.SetDirty(AddressableAssetSettings.ModificationEvent.LabelAdded, entry, false, true);
}
private class MakeAddressableFixArgs
{
[ValueDropdown(nameof(GetGroups))]
[OnInspectorInit(nameof(SelectDefault))]
public AddressableAssetGroup Group;
private void SelectDefault()
{
this.Group = AddressableAssetSettingsDefaultObject.SettingsExists
? AddressableAssetSettingsDefaultObject.Settings.DefaultGroup
: null;
}
private static IEnumerable<ValueDropdownItem> GetGroups()
{
return !AddressableAssetSettingsDefaultObject.SettingsExists
? Enumerable.Empty<ValueDropdownItem>()
: AddressableAssetSettingsDefaultObject.Settings.groups
.Where(group => !group.ReadOnly)
.Select(group => new ValueDropdownItem(group.Name, group));
}
[Button(SdfIconType.ListNested), PropertySpace(8f)]
private void OpenAddressablesGroups() => OdinAddressableUtility.OpenGroupsWindow();
}
private class AddLabelsFixArgs
{
[HideIf("@true")]
public List<AssetLabel> AssetLabels
{
get
{
if (!AddressableAssetSettingsDefaultObject.SettingsExists) return this.assetLabels;
var settings = AddressableAssetSettingsDefaultObject.Settings;
var labels = settings
.GetLabels()
.Select(l => new AssetLabel { Label = l, Toggled = false })
.ToList();
foreach (var assetLabel in this.assetLabels)
{
var label = labels.FirstOrDefault(l => l.Label == assetLabel.Label);
if (label != null)
{
label.Toggled = assetLabel.Toggled;
}
}
this.assetLabels = labels;
return this.assetLabels;
}
}
private List<AssetLabel> assetLabels = new List<AssetLabel>();
[OnInspectorGUI]
private void Draw()
{
var togglesRect = EditorGUILayout.GetControlRect(false, Mathf.CeilToInt(this.AssetLabels.Count / 2f) * 20f);
for (var i = 0; i < this.AssetLabels.Count; i++)
{
var assetLabel = this.AssetLabels[i];
var toggleRect = togglesRect.SplitGrid(togglesRect.width / 2f, 20, i);
assetLabel.Toggled = GUI.Toggle(toggleRect, assetLabel.Toggled, assetLabel.Label);
}
if (!AddressableAssetSettingsDefaultObject.SettingsExists) return;
GUILayout.Space(8f);
var buttonsRect = EditorGUILayout.GetControlRect(false, 20f);
if (SirenixEditorGUI.SDFIconButton(buttonsRect, "Open Addressables Labels", SdfIconType.TagsFill))
{
OdinAddressableUtility.OpenLabelsWindow();
}
}
}
private class AssetLabel
{
public bool Toggled;
public string Label;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b44b08a1f58a83149988fde5ac600fe4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,205 @@
//-----------------------------------------------------------------------
// <copyright file="CheckDuplicateBundleDependenciesValidator.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
#if UNITY_EDITOR && ODIN_VALIDATOR_3_1
#if !SIRENIX_INTERNAL
#pragma warning disable
#endif
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEditor.AddressableAssets;
using Sirenix.OdinInspector.Editor.Validation;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using UnityEditor.AddressableAssets.Settings;
using System.Collections;
using System;
using Sirenix.OdinValidator.Editor;
using Sirenix.OdinInspector.Modules.Addressables.Editor;
[assembly: RegisterValidationRule(typeof(CheckDuplicateBundleDependenciesValidator),
Description = "This validator detects potential duplicate asset dependencies in an addressable group, without the need for a build. " +
"For instance, imagine two prefabs in separate groups, both referencing the same material. Each group would then include the material " +
"and all its associated dependencies. " +
"To address this, the material should be marked as Addressable, either with one of the prefabs or in a distinct group.\n\n" +
"<b>Fixes: </b>Executing the fix will make the dependency addressable and move it to the specified group.\n\n" +
"<b>Exceptions: </b>It's important to note that duplicate assets aren't inherently problematic. For example, if certain assets are " +
"never accessed by the same user group, such as region-specific assets, these duplications might be desired or at least inconsequential. " +
"As every project is unique, decisions concerning duplicate asset dependencies should be considered on a case-by-case basis.")]
namespace Sirenix.OdinInspector.Modules.Addressables.Editor
{
public class CheckDuplicateBundleDependenciesValidator : GlobalValidator
{
private static Dictionary<GUID, List<string>> dependencyGroupMap = new Dictionary<GUID, List<string>>();
[Tooltip("The severity of the validation result.")]
public ValidatorSeverity ValidatorSeverity = ValidatorSeverity.Warning;
[Tooltip("Assets to ignore when validating.")]
[LabelText("Ignored GUIDs"), CustomValueDrawer(nameof(DrawGUIDEntry))]
public List<string> IgnoredGUIDs = new List<string>();
public override IEnumerable RunValidation(ValidationResult result)
{
dependencyGroupMap.Clear();
var addressableAssetSettings = AddressableAssetSettingsDefaultObject.Settings;
if (addressableAssetSettings == null) yield break;
foreach (var addressableAssetGroup in addressableAssetSettings.groups)
{
if (addressableAssetGroup == null) continue;
foreach (var addressableAssetEntry in addressableAssetGroup.entries)
{
var dependencyAssetPaths = AssetDatabase.GetDependencies(addressableAssetEntry.AssetPath)
.Where(assetPath => !assetPath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) &&
!assetPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase));
foreach (var dependencyAssetPath in dependencyAssetPaths)
{
var dependencyGUID = new GUID(AssetDatabase.AssetPathToGUID(dependencyAssetPath));
if (this.IgnoredGUIDs.Contains(dependencyGUID.ToString())) continue;
var dependencyAddressableAssetEntry = addressableAssetSettings.FindAssetEntry(dependencyGUID.ToString());
var isAddressable = dependencyAddressableAssetEntry != null;
if (isAddressable) continue;
if (!dependencyGroupMap.ContainsKey(dependencyGUID))
{
dependencyGroupMap.Add(dependencyGUID, new List<string>());
}
if (!dependencyGroupMap[dependencyGUID].Contains(addressableAssetGroup.Name))
{
dependencyGroupMap[dependencyGUID].Add(addressableAssetGroup.Name);
}
}
}
}
foreach (var kvp in dependencyGroupMap)
{
var dependencyGUID = kvp.Key;
var groups = kvp.Value;
if (groups.Count > 1)
{
var assetPath = AssetDatabase.GUIDToAssetPath(dependencyGUID.ToString());
var message = $"{assetPath} is duplicated in these groups: {string.Join(", ", groups)}";
result.Add(this.ValidatorSeverity, message).WithFix<FixArgs>(args =>
{
if (args.FixChoice == FixChoice.Ignore)
{
var sourceType = args.IgnoreForEveryone ? ConfigSourceType.Project : ConfigSourceType.Local;
var data = RuleConfig.Instance.GetRuleData<CheckDuplicateBundleDependenciesValidator>(sourceType);
data.IgnoredGUIDs.Add(dependencyGUID.ToString());
RuleConfig.Instance.SetAndSaveRuleData(data, sourceType);
return;
}
var obj = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object));
AddressableAssetGroup group;
if (args.Group == "Create New Group")
{
if (args.GroupName.IsNullOrWhitespace()) return;
group = addressableAssetSettings.FindGroup(args.GroupName);
if (group == null)
{
group = addressableAssetSettings.CreateGroup(args.GroupName, false, false, false, null);
}
}
else
{
group = addressableAssetSettings.FindGroup(args.Group);
if (group == null)
{
group = addressableAssetSettings.CreateGroup(args.Group, false, false, false, null);
}
}
OdinAddressableUtility.MakeAddressable(obj, group);
}, false).WithModifyRuleDataContextClick<CheckDuplicateBundleDependenciesValidator>("Ignore", data =>
{
data.IgnoredGUIDs.Add(dependencyGUID.ToString());
}).SetSelectionObject(AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(AssetDatabase.GUIDToAssetPath(dependencyGUID.ToString())));
}
}
}
private string DrawGUIDEntry(string guid)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
EditorGUILayout.TextArea(assetPath, SirenixGUIStyles.MultiLineLabel);
EditorGUILayout.TextField(guid);
return guid;
}
private enum FixChoice
{
AddToGroup,
Ignore,
}
private class FixArgs
{
[EnumToggleButtons, HideLabel]
public FixChoice FixChoice;
[PropertySpace(10)]
[ValueDropdown("Groups")]
//[Title("Group To Add To", TitleAlignment = TitleAlignments.Centered)]
[ShowIf(nameof(FixChoice), FixChoice.AddToGroup, Animate = false)]
public string Group = "Duplicate Asset Isolation";
[ValidateInput(nameof(ValidateGroupName), "The group name cannot be empty")]
[ShowIf(nameof(ShowNewGroupName), Animate = false)]
public string GroupName;
[LabelWidth(120f)]
[PropertySpace(10)]
[ShowIf("FixChoice", FixChoice.Ignore, Animate = false)]
public bool IgnoreForEveryone = true;
[OnInspectorGUI]
[PropertySpace(10)]
[DetailedInfoBox("Note that duplicate assets may not always be an issue", "Note that duplicate assets may not always be an issue. If assets will never be requested by the same set of users (for example, region-specific assets), then duplicate dependencies may be desired, or at least inconsequential. Each Project is unique, so fixing duplicate asset dependencies should be evaluated on a case by case basis")]
private void Dummy() { }
private bool ShowNewGroupName => this.FixChoice != FixChoice.Ignore && this.Group == "Create New Group";
private bool ValidateGroupName() => !this.GroupName.IsNullOrWhitespace();
private IEnumerable<string> Groups()
{
var addressableAssetSettings = AddressableAssetSettingsDefaultObject.Settings;
return addressableAssetSettings == null
? Enumerable.Empty<string>()
: addressableAssetSettings.groups
.Where(group => group != null && group.Name != "Built In Data")
.Select(group => group.Name)
.Append("Duplicate Asset Isolation")
.Prepend("Create New Group");
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bdc8ee2cf75a17644a0bd81a965cc2e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,175 @@
//-----------------------------------------------------------------------
// <copyright file="CheckResourcesToAddressableDuplicateDependenciesValidator.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
#if UNITY_EDITOR && ODIN_VALIDATOR_3_1
#if !SIRENIX_INTERNAL
#pragma warning disable
#endif
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEditor.AddressableAssets;
using Sirenix.OdinInspector.Editor.Validation;
using Sirenix.Utilities.Editor;
using System.Collections;
using System;
using System.IO;
using Sirenix.OdinValidator.Editor;
using Sirenix.OdinInspector.Modules.Addressables.Editor;
[assembly: RegisterValidationRule(typeof(CheckResourcesToAddressableDuplicateDependenciesValidator),
Description = "This validator identifies dependencies that are duplicated in both addressable groups and the \"Resources\" folder.\n\n" +
"These duplications mean that data will be included in both the application build and the addressables build.\n\n" +
"You can decide to simply ignore these duplicated dependencies if this behavior is desired, or use the provided fix " +
"to move the asset outside of the \"Resources\" folder.")]
namespace Sirenix.OdinInspector.Modules.Addressables.Editor
{
public class CheckResourcesToAddressableDuplicateDependenciesValidator : GlobalValidator
{
[Tooltip("The severity of the validation result.")]
public ValidatorSeverity ValidatorSeverity = ValidatorSeverity.Warning;
[Tooltip("Assets to ignore when validating.")]
[LabelText("Ignored GUIDs"), CustomValueDrawer(nameof(DrawGUIDEntry))]
public List<string> IgnoredGUIDs = new List<string>();
public override IEnumerable RunValidation(ValidationResult result)
{
var addressableAssetSettings = AddressableAssetSettingsDefaultObject.Settings;
if (addressableAssetSettings == null) yield break;
foreach (var addressableAssetGroup in addressableAssetSettings.groups)
{
if (addressableAssetGroup == null) continue;
foreach (var addressableAssetEntry in addressableAssetGroup.entries)
{
var dependencyAssetPaths = AssetDatabase.GetDependencies(addressableAssetEntry.AssetPath)
.Where(assetPath => !assetPath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) &&
!assetPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase));
foreach (var dependencyAssetPath in dependencyAssetPaths)
{
var dependencyGUID = new GUID(AssetDatabase.AssetPathToGUID(dependencyAssetPath));
if (this.IgnoredGUIDs.Contains(dependencyGUID.ToString())) continue;
var dependencyAddressableAssetEntry = addressableAssetSettings.FindAssetEntry(dependencyGUID.ToString());
var isAddressable = dependencyAddressableAssetEntry != null;
if (isAddressable) continue;
if (!IsInsideResourcesFolder(dependencyAssetPath)) continue;
result.Add(this.ValidatorSeverity, $"{dependencyAssetPath} is duplicated in addressable data and resource folders.")
.WithFix<FixArgs>(args =>
{
if (args.FixChoice == FixChoice.Ignore)
{
var sourceType = args.IgnoreForEveryone ? ConfigSourceType.Project : ConfigSourceType.Local;
var data = RuleConfig.Instance.GetRuleData<CheckResourcesToAddressableDuplicateDependenciesValidator>(sourceType);
data.IgnoredGUIDs.Add(dependencyGUID.ToString());
RuleConfig.Instance.SetAndSaveRuleData(data, sourceType);
return;
}
if (!ValidNewFolder(args.NewFolder, out _)) return;
if (!AssetDatabase.IsValidFolder(args.NewFolder))
{
Directory.CreateDirectory(new DirectoryInfo(args.NewFolder).FullName);
AssetDatabase.Refresh();
}
var newPath = $"{args.NewFolder}/{Path.GetFileName(dependencyAssetPath)}";
AssetDatabase.MoveAsset(dependencyAssetPath, newPath);
}, false).WithModifyRuleDataContextClick<CheckResourcesToAddressableDuplicateDependenciesValidator>("Ignore", data =>
{
data.IgnoredGUIDs.Add(dependencyGUID.ToString());
}).SetSelectionObject(AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(AssetDatabase.GUIDToAssetPath(dependencyGUID.ToString())));
yield break;
}
}
}
}
private string DrawGUIDEntry(string guid)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
EditorGUILayout.TextArea(assetPath, SirenixGUIStyles.MultiLineLabel);
EditorGUILayout.TextField(guid);
return guid;
}
private static bool IsInsideResourcesFolder(string path)
{
var pathElements = path.Split('/');
foreach (var pathElement in pathElements)
{
if (pathElement.Equals("Resources", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
private static bool ValidNewFolder(string path, out string message)
{
if (IsInsideResourcesFolder(path))
{
message = "The asset cannot be moved into a 'Resources' folder";
return false;
}
if (!path.StartsWith("Assets/"))
{
message = "The asset must be inside the 'Assets' folder";
return false;
}
message = "The folder is valid";
return true;
}
private enum FixChoice
{
MoveAsset,
Ignore,
}
private class FixArgs
{
[HideLabel]
[EnumToggleButtons]
public FixChoice FixChoice;
[FolderPath]
[PropertySpace(10)]
[ValidateInput(nameof(ValidateFolderPath))]
[ShowIf("FixChoice", FixChoice.MoveAsset, Animate = false)]
public string NewFolder = "Assets/Resources_moved";
[LabelWidth(120f)]
[PropertySpace(10)]
[ShowIf("FixChoice", FixChoice.Ignore, Animate = false)]
public bool IgnoreForEveryone = true;
private bool ValidateFolderPath(string path, ref string message)
{
return ValidNewFolder(path, out message);
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f75aebe03a9aa4a4b82d2b54dcc34de5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,38 @@
//-----------------------------------------------------------------------
// <copyright file="DisallowAddressableSubAssetFieldAttributeValidator.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
#if UNITY_EDITOR
#if !SIRENIX_INTERNAL
#pragma warning disable
#endif
using Sirenix.OdinInspector.Editor.Validation;
using Sirenix.OdinInspector.Modules.Addressables.Editor;
using UnityEngine.AddressableAssets;
[assembly: RegisterValidator(typeof(DisallowAddressableSubAssetFieldAttributeValidator))]
namespace Sirenix.OdinInspector.Modules.Addressables.Editor
{
/// <summary>
/// Validator for the DisallowAddressableSubAssetFieldAttribute.
/// </summary>
public class DisallowAddressableSubAssetFieldAttributeValidator : AttributeValidator<DisallowAddressableSubAssetFieldAttribute, AssetReference>
{
protected override void Validate(ValidationResult result)
{
if (this.Value != null && string.IsNullOrEmpty(this.Value.SubObjectName) == false)
{
result.AddError("Sub-asset references is not allowed on this field.")
.WithFix("Remove Sub-Asset", () => this.Value.SubObjectName = null, true);
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5775d33ffc1143149c4f425f693b04b6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,59 @@
//-----------------------------------------------------------------------
// <copyright file="MissingAddressableGroupReferenceValidator.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
#if UNITY_EDITOR && ODIN_VALIDATOR_3_1
#if !SIRENIX_INTERNAL
#pragma warning disable
#endif
using System.Collections.Generic;
using UnityEditor.AddressableAssets;
using Sirenix.OdinInspector.Editor.Validation;
using UnityEditor.AddressableAssets.Settings;
using System.Collections;
using Sirenix.OdinInspector.Modules.Addressables.Editor;
[assembly: RegisterValidator(typeof(MissingAddressableGroupReferenceValidator))]
namespace Sirenix.OdinInspector.Modules.Addressables.Editor
{
public class MissingAddressableGroupReferenceValidator : GlobalValidator
{
public override IEnumerable RunValidation(ValidationResult result)
{
var addressableAssetSettings = AddressableAssetSettingsDefaultObject.Settings;
if (addressableAssetSettings == null) yield break;
var missingGroupIndices = new List<int>();
for (var i = 0; i < addressableAssetSettings.groups.Count; i++)
{
var group = addressableAssetSettings.groups[i];
if (group == null)
{
missingGroupIndices.Add(i);
}
}
if (missingGroupIndices.Count > 0)
{
result.Add(ValidatorSeverity.Error, "Addressable groups contains missing references").WithFix("Delete missing reference", () =>
{
for (var i = missingGroupIndices.Count - 1; i >= 0; i--)
{
addressableAssetSettings.groups.RemoveAt(missingGroupIndices[i]);
addressableAssetSettings.SetDirty(AddressableAssetSettings.ModificationEvent.GroupRemoved, null, true, true);
}
});
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4ae55abdaf19ef4498d1a2fbe1bd9f9b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
ManifestVersion: 1
ModuleID: Unity.Addressables
ModuleVersion: 1.1.0.11
ModuleFiles:
AddressablesInspectors.cs
AddressablesInspectors.cs.meta
Internal.meta
Sirenix.OdinInspector.Modules.Unity.Addressables.asmdef
Sirenix.OdinInspector.Modules.Unity.Addressables.asmdef.meta
Validators.meta
Internal/OdinAddressableReflection.cs
Internal/OdinAddressableReflection.cs.meta
Internal/OdinAddressableReflectionValidator.cs
Internal/OdinAddressableReflectionValidator.cs.meta
Validators/AssetLabelReferenceValidator.cs
Validators/AssetLabelReferenceValidator.cs.meta
Validators/AssetReferenceValidator.cs
Validators/AssetReferenceValidator.cs.meta
Validators/CheckDuplicateBundleDependenciesValidator.cs
Validators/CheckDuplicateBundleDependenciesValidator.cs.meta
Validators/CheckResourcesToAddressableDuplicateDependenciesValidator.cs
Validators/CheckResourcesToAddressableDuplicateDependenciesValidator.cs.meta
Validators/DisallowAddressableSubAssetFieldAttributeValidator.cs
Validators/DisallowAddressableSubAssetFieldAttributeValidator.cs.meta
Validators/MissingAddressableGroupReferenceValidator.cs
Validators/MissingAddressableGroupReferenceValidator.cs.meta

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3459a30abd1a4e979488e906febc27e7
timeCreated: 1707714315

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c09625914af7d2d4e92c3653d5f15bd7
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,173 @@
//-----------------------------------------------------------------------
// <copyright file="OdinLocalizationConfig.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using Sirenix.OdinInspector.Editor;
using Sirenix.OdinInspector.Modules.Localization.Editor.Internal;
using Sirenix.Reflection.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using UnityEditor;
using UnityEngine;
namespace Sirenix.OdinInspector.Modules.Localization.Editor.Configs
{
[GlobalConfig("Plugins/Sirenix/Odin Inspector/Modules/Unity.Localization/Editor/Configs")]
public class OdinLocalizationConfig : GlobalConfig<OdinLocalizationConfig>
{
public class ThemeColorDrawer : OdinValueDrawer<ThemeColor>
{
protected override void Initialize()
{
base.Initialize();
this.Property.State.Expanded = false;
}
protected override void DrawPropertyLayout(GUIContent label)
{
SirenixEditorGUI.BeginBox(string.Empty);
{
SirenixEditorGUI.BeginBoxHeader();
{
GUILayout_Internal.BeginRow();
{
GUILayout_Internal.BeginColumn(LayoutSize.Pixels(EditorGUIUtility.labelWidth + 6.0f));
{
this.Property.State.Expanded = EditorGUILayout.Foldout(this.Property.State.Expanded,
$"{label.text} ({(EditorGUIUtility.isProSkin ? "Dark" : "Light")})",
true);
}
GUILayout_Internal.EndColumn();
GUILayout_Internal.BeginColumn(LayoutSize.Auto);
{
this.Property.Children[nameof(ThemeColor.Color)].Draw(null);
}
GUILayout_Internal.EndColumn();
}
GUILayout_Internal.EndRow();
}
SirenixEditorGUI.EndBoxHeader();
bool toggle = this.ValueEntry.ValueState != PropertyValueState.NullReference && this.Property.State.Expanded;
if (SirenixEditorGUI.BeginFadeGroup(this, toggle))
{
GUILayout.BeginHorizontal();
this.Property.Children[nameof(ThemeColor.lightColor)].Draw();
GUILayout.Space(3.5f);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
this.Property.Children[nameof(ThemeColor.darkColor)].Draw();
GUILayout.Space(3.5f);
GUILayout.EndHorizontal();
}
SirenixEditorGUI.EndFadeGroup();
}
SirenixEditorGUI.EndBox();
if (this.Property.State.Expanded)
{
GUILayout.Space(4.0f);
}
}
}
[Serializable]
public class ThemeColor
{
[ShowInInspector]
public Color Color
{
get => EditorGUIUtility.isProSkin ? this.darkColor : this.lightColor;
set
{
if (EditorGUIUtility.isProSkin)
{
this.darkColor = value;
}
else
{
this.lightColor = value;
}
}
}
public Color lightColor;
public Color darkColor;
public ThemeColor(Color lightColor, Color darkColor)
{
this.lightColor = lightColor;
this.darkColor = darkColor;
}
public static implicit operator Color(ThemeColor color) => color.Color;
}
[ShowInInspector]
[BoxGroup("User Interface")]
[Range(96, 1024)]
public int assetRowHeight = 128;
[BoxGroup("Syntax Highlighting")]
public bool useSyntaxHighlighter = true;
[EnableIf(nameof(useSyntaxHighlighter))]
[BoxGroup("Syntax Highlighting")]
public ThemeColor placeholderColor = new ThemeColor(new Color(0.743147f, 0.9433962f, 0.9242815f), new Color(0, 0.5882353f, 0.5333334f));
[EnableIf(nameof(useSyntaxHighlighter))]
[BoxGroup("Syntax Highlighting")]
public ThemeColor selectorColor = new ThemeColor(new Color(1.0f, 0.7727525f, 0.3632075f), new Color(1, 0.6470588f, 0));
[EnableIf(nameof(useSyntaxHighlighter))]
[BoxGroup("Syntax Highlighting")]
public ThemeColor formatterColor = new ThemeColor(new Color(0.9921569f, 0.9855571f, 0.8823529f), new Color(0.9607843f, 0.9607843f, 0.8627451f));
[BoxGroup("Navigation")]
[Range(1, 1000.0f)]
public float scrollSpeed = 24.0f;
[BoxGroup("Navigation")]
public bool invertMouseDragNavigation = true;
[BoxGroup("Navigation")]
[Range(0.5f, 5.0f)]
public float mouseDragSpeed = 1.0f;
[InfoBox("We couldn't find the necessary methods/classes to perform custom undo operations, therefore this option has been disabled and will be considered false even if true.",
VisibleIf = "@!OdinLocalizationReflectionValues.HasAPIForCustomUndo")]
[EnableIf("@OdinLocalizationReflectionValues.HasAPIForCustomUndo")]
[BoxGroup("Undo")]
public bool useCustomUndoHandlingForAssetCollections = true;
[Button(ButtonSizes.Large)]
public void Reset()
{
if (!EditorUtility.DisplayDialog("Odin Localization Config", "Are you certain you want to reset your Localization config?", "Yes", "No"))
{
return;
}
this.useCustomUndoHandlingForAssetCollections = OdinLocalizationReflectionValues.HasAPIForCustomUndo;
this.assetRowHeight = 128;
this.useSyntaxHighlighter = true;
this.placeholderColor = new ThemeColor(new Color(0.743147f, 0.9433962f, 0.9242815f), new Color(0, 0.5882353f, 0.5333334f));
this.selectorColor = new ThemeColor(new Color(1.0f, 0.7727525f, 0.3632075f), new Color(1, 0.6470588f, 0));
this.formatterColor = new ThemeColor(new Color(0.9921569f, 0.9855571f, 0.8823529f), new Color(0.9607843f, 0.9607843f, 0.8627451f));
this.scrollSpeed = 24.0f;
this.invertMouseDragNavigation = true;
this.mouseDragSpeed = 1.0f;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2fc91909e6444cb29aacb664f2098c7e
timeCreated: 1707714331

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bf930ee5d27d4f90ab84f23d337eaaca
timeCreated: 1704811167

View File

@@ -0,0 +1,993 @@
//-----------------------------------------------------------------------
// <copyright file="LocalizationMetadata.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Sirenix.OdinInspector.Editor;
using Sirenix.OdinInspector.Modules.Localization.Editor.Internal;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using UnityEditor;
using UnityEditor.Localization;
using UnityEngine;
using UnityEngine.Localization.Metadata;
using UnityEngine.Localization.Tables;
namespace Sirenix.OdinInspector.Modules.Localization.Editor
{
public class LocalizationMetadata
{
private object lastTarget;
public object Target;
private LocalizationTableCollection localizationTableCollection;
private SearchField searchField = new SearchField();
private string searchTerm;
private Vector2 scrollPosition;
private IMetadata toBeAdded;
private IMetadata toBeRemoved;
private Type lastMetadataTypeUnderCursor;
private StringBuilder stringBuilder = new StringBuilder();
private OdinLocalizationEditorWindow.WindowState windowState;
private List<Type> tags = new List<Type>
{
typeof(ExcludeEntryFromExport)
};
private GUIStyle _contentPadding;
private GUIStyle ContentPadding =>
this._contentPadding = this._contentPadding ?? new GUIStyle
{
padding = new RectOffset(10, 10, 10, 10)
};
private GUIStyle _platformOverridePadding;
private GUIStyle PlatformOverridePadding =>
this._platformOverridePadding = this._platformOverridePadding ?? new GUIStyle
{
padding = new RectOffset(10, 20, 10, 10)
};
public LocalizationMetadata(LocalizationTableCollection localizationTableCollection, OdinLocalizationEditorWindow.WindowState windowState)
{
this.localizationTableCollection = localizationTableCollection;
this.windowState = windowState;
}
/// <summary>
/// Adds the given metadata to the shared data of the <see cref="LocalizationTableCollection"/>.
/// </summary>
public void Add(IMetadata metadata)
{
this.localizationTableCollection.SharedData.Metadata.AddMetadata(metadata);
}
/// <summary>
/// Adds the given metadata to the given <see cref="LocalizationTable"/>.
/// </summary>
public void Add(LocalizationTable localizationTable, IMetadata metadata)
{
localizationTable.AddMetadata(metadata);
}
/// <summary>
/// Adds the given metadata to the given <see cref="SharedTableData"/>.
/// </summary>
public void Add(SharedTableData.SharedTableEntry sharedTableEntry, IMetadata metadata)
{
sharedTableEntry.Metadata.AddMetadata(metadata);
}
/// <summary>
/// Adds the given metadata to the given <see cref="TableEntry"/>.
/// </summary>
public void Add(TableEntry tableEntry, IMetadata metadata)
{
tableEntry.AddMetadata(metadata);
}
/// <summary>
/// Removes the given metadata from the shared data of the <see cref="LocalizationTableCollection"/>.
/// </summary>
public void Remove(IMetadata metadata)
{
this.localizationTableCollection.SharedData.Metadata.RemoveMetadata(metadata);
}
/// <summary>
/// Removes the given metadata from the given <see cref="LocalizationTable"/>.
/// </summary>
public void Remove(LocalizationTable localizationTable, IMetadata metadata)
{
localizationTable.RemoveMetadata(metadata);
}
/// <summary>
/// Removes the given metadata from the given <see cref="SharedTableData"/>.
/// </summary>
public void Remove(SharedTableData.SharedTableEntry sharedTableEntry, IMetadata metadata)
{
sharedTableEntry.Metadata.RemoveMetadata(metadata);
}
/// <summary>
/// Removes the given metadata from the given <see cref="TableEntry"/>.
/// </summary>
public void Remove(TableEntry tableEntry, IMetadata metadata)
{
tableEntry.RemoveMetadata(metadata);
}
/// <summary>
/// Draws the metadata for the given <see cref="LocalizationTableCollection"/>
/// </summary>
public float Draw(Rect rect)
{
var minWidth = 0f;
if (this.Target == null) return minWidth;
if (Event.current.type != EventType.Repaint)
{
if (this.toBeAdded != null)
{
switch (this.Target)
{
case LocalizationTable localizationTable:
Undo.RecordObject(this.localizationTableCollection, "Localization Tables Metadata Added");
this.Add(localizationTable, this.toBeAdded);
break;
case SharedTableData.SharedTableEntry sharedTableEntry:
Undo.RecordObject(this.localizationTableCollection.SharedData, "Localization Tables Metadata Added");
this.Add(sharedTableEntry, this.toBeAdded);
break;
case TableEntry tableEntry:
Undo.RecordObject(tableEntry.Table, "Localization Tables Metadata Added");
this.Add(tableEntry, this.toBeAdded);
break;
default:
Undo.RecordObject(this.localizationTableCollection.SharedData, "Localization Tables Metadata Added");
this.Add(this.toBeAdded);
break;
}
this.toBeAdded = null;
this.windowState.Dispose();
}
if (this.toBeRemoved != null)
{
switch (this.Target)
{
case LocalizationTable localizationTable:
Undo.RecordObject(this.localizationTableCollection, "Localization Tables Metadata Removed");
this.Remove(localizationTable, this.toBeRemoved);
break;
case SharedTableData.SharedTableEntry sharedTableEntry:
Undo.RecordObject(this.localizationTableCollection.SharedData, "Localization Tables Metadata Removed");
this.Remove(sharedTableEntry, this.toBeRemoved);
break;
case TableEntry tableEntry:
Undo.RecordObject(tableEntry.Table, "Localization Tables Metadata Removed");
this.Remove(tableEntry, this.toBeRemoved);
break;
default:
Undo.RecordObject(this.localizationTableCollection.SharedData, "Localization Tables Metadata Removed");
this.Remove(this.toBeRemoved);
break;
}
this.toBeRemoved = null;
this.windowState.Dispose();
}
}
if (this.windowState.MetadataTree == null || this.lastTarget != this.Target)
{
this.windowState.MetadataTree?.Dispose();
switch (this.Target)
{
case LocalizationTable localizationTable:
this.windowState.MetadataTree = PropertyTree.Create(localizationTable);
break;
case SharedTableData.SharedTableEntry sharedTableEntry:
this.windowState.MetadataTree = PropertyTree.Create(sharedTableEntry);
break;
case TableEntry tableEntry:
{
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var entryData = typeof(TableEntry).GetProperty("Data", bindingFlags).GetValue(tableEntry);
this.windowState.MetadataTree = PropertyTree.Create(entryData);
break;
}
case LocalizationTableCollection localizationTableCollection:
this.windowState.MetadataTree = PropertyTree.Create(localizationTableCollection.SharedData);
break;
}
this.lastTarget = this.Target;
}
var metadataCollection = this.windowState.MetadataTree.RootProperty.Children["m_Metadata"];
var metadataEntries = metadataCollection.Children["m_Items"].Children.ToList();
metadataEntries = metadataEntries
.Where(entry => !entry.Attributes.HasAttribute<HideInInspector>())
.OrderBy(entry => !this.tags.Contains(entry.ValueEntry.TypeOfValue))
.ThenBy(entry => entry.Name)
.ToList();
this.windowState.MetadataTree.BeginDraw(false);
switch (this.Target)
{
case LocalizationTable localizationTable:
minWidth = this.Draw(rect, localizationTable, "Table", metadataEntries, m => this.Add(localizationTable, m), m => this.Remove(localizationTable, m), () => this.OpenMetadataSelector(localizationTable));
break;
case SharedTableData.SharedTableEntry sharedTableEntry:
minWidth = this.Draw(rect, this.localizationTableCollection.SharedData, "Shared Entry", metadataEntries, m => this.Add(sharedTableEntry, m), m => this.Remove(sharedTableEntry, m), () => this.OpenMetadataSelector(sharedTableEntry));
break;
case TableEntry tableEntry:
minWidth = this.Draw(rect, tableEntry.Table, "Entry", metadataEntries, m => this.Add(tableEntry, m), m => this.Remove(tableEntry, m), () => this.OpenMetadataSelector(tableEntry));
break;
default:
minWidth = this.Draw(rect, this.localizationTableCollection.SharedData, "Shared Table", metadataEntries, m => this.Add(m), m => this.Remove(m), () => this.OpenMetadataSelector(this.localizationTableCollection));
break;
}
this.windowState.MetadataTree.EndDraw();
return minWidth;
}
private float Draw(Rect rect, UnityEngine.Object target, string title, List<InspectorProperty> metadataEntries, Action<IMetadata> add, Action<IMetadata> remove, Action openMetadataSelector)
{
const int headerHeight = 22;
const int addButtonAreaHeight = 40;
EditorGUIUtility.labelWidth = 0f;
// We only want to process events if the user's mouse is currently
// in the metadata panel so we grab some information for later.
var metadataPanelHasFocus = Event.current.IsMouseOver(rect);
var holdingControl = Event.current.control && metadataPanelHasFocus;
var holdingAlt = Event.current.alt && metadataPanelHasFocus;
if (Event.current.OnKeyUp(KeyCode.LeftAlt))
{
this.lastMetadataTypeUnderCursor = null;
}
var searchAreaRect = rect.TakeFromTop(32);
EditorGUI.DrawRect(searchAreaRect.AlignBottom(1), SirenixGUIStyles.BorderColor);
// Draw search field.
if (searchAreaRect.width > 125)
{
var titleWidth = SirenixGUIStyles.LabelCentered.CalcWidth(title) + 13;
var titleRect = searchAreaRect.AlignRight(titleWidth);
EditorGUI.DrawRect(titleRect.TakeFromLeft(1), SirenixGUIStyles.BorderColor);
this.searchTerm = this.searchField.Draw(searchAreaRect.SubXMax(titleWidth).Padding(6), this.searchTerm,
"Search for metadata...");
GUI.Label(titleRect, title, SirenixGUIStyles.LabelCentered);
}
else
{
GUI.Label(searchAreaRect, title, SirenixGUIStyles.LabelCentered);
}
var filteredMetadataEntries = string.IsNullOrWhiteSpace(this.searchTerm)
? metadataEntries.ToList()
: metadataEntries.Where(metadataEntry =>
{
if (FuzzySearch.Contains(this.searchTerm, metadataEntry.Info.TypeOfOwner.Name))
{
return true;
}
if (FuzzySearch.Contains(this.searchTerm, metadataEntry.ValueEntry?.WeakSmartValue?.ToString()))
{
return true;
}
foreach (var child in metadataEntry.Children.Recurse())
{
if (FuzzySearch.Contains(this.searchTerm, child.Name))
{
return true;
}
if (FuzzySearch.Contains(this.searchTerm, child.ValueEntry?.WeakSmartValue?.ToString()))
{
return true;
}
}
return false;
}).ToList();
var metadataViewHeight = rect.height - addButtonAreaHeight;
GUILayout.BeginArea(rect.AlignTop(metadataViewHeight));
this.scrollPosition = EditorGUILayout.BeginScrollView(this.scrollPosition, GUIStyle.none, GUI.skin.verticalScrollbar);
GUIHelper.BeginLayoutMeasuring();
{
for (var i = 0; i < filteredMetadataEntries.Count; i++)
{
var metadataEntry = filteredMetadataEntries[i];
var metadataHeaderRect = GUILayoutUtility.GetRect(0, headerHeight, GUILayoutOptions.ExpandWidth().ExpandHeight(false));
var unchangedMetadataHeaderRect = metadataHeaderRect;
var genericMenu = new GenericMenu();
genericMenu.AddItem(new GUIContent("Delete"), false, () =>
{
var metadata = (IMetadata)metadataEntry.ValueEntry.WeakSmartValue;
this.toBeRemoved = metadata;
});
var isTag = this.tags.Contains(metadataEntry.ValueEntry.TypeOfValue);
if (!isTag)
{
genericMenu.AddItem(new GUIContent("Copy"), false, () =>
{
var metadata = (IMetadata)metadataEntry.ValueEntry.WeakSmartValue;
Clipboard.Copy(metadata);
});
if (Clipboard.CanPaste<IMetadata>())
genericMenu.AddItem(new GUIContent("Paste"), false, () =>
{
var metadata = (IMetadata)Clipboard.Paste();
this.toBeAdded = metadata;
});
else
genericMenu.AddDisabledItem(new GUIContent("Paste"));
genericMenu.AddSeparator("");
genericMenu.AddItem(new GUIContent($"{(metadataEntry.State.Expanded ? "Collapse" : "Expand")} All (Ctrl-Click)"), false, () =>
{
var newState = !metadataEntry.State.Expanded;
foreach (var entry in filteredMetadataEntries) entry.State.Expanded = newState;
});
genericMenu.AddItem(new GUIContent($"{(metadataEntry.State.Expanded ? "Collapse" : "Expand")} All Of Same Type (Alt-Click)"), false, () =>
{
var newState = !metadataEntry.State.Expanded;
foreach (var entry in filteredMetadataEntries.Where(m => m.Info.TypeOfOwner == metadataEntry.Info.TypeOfOwner)) entry.State.Expanded = newState;
});
}
Color metadataHeaderBackground = new Color(0.243f, 0.243f, 0.243f);
if (!isTag && holdingControl)
{
metadataHeaderBackground = new Color(0.278f, 0.278f, 0.278f);
GUIHelper.RequestRepaint();
}
else if (!isTag && holdingAlt)
{
if (Event.current.IsMouseOver(unchangedMetadataHeaderRect))
{
this.lastMetadataTypeUnderCursor = metadataEntry.ValueEntry.TypeOfValue;
GUIHelper.RequestRepaint();
}
metadataHeaderBackground = this.lastMetadataTypeUnderCursor == metadataEntry.ValueEntry.TypeOfValue
? new Color(0.278f, 0.278f, 0.278f)
: new Color(0.243f, 0.243f, 0.243f);
}
if (Event.current.IsMouseOver(unchangedMetadataHeaderRect))
{
metadataHeaderBackground = new Color(0.278f, 0.278f, 0.278f);
}
EditorGUI.DrawRect(unchangedMetadataHeaderRect, metadataHeaderBackground);
if (i != 0)
{
EditorGUI.DrawRect(unchangedMetadataHeaderRect.AlignTop(1), new Color(0.102f, 0.102f, 0.102f));
}
var foldoutTriangleRect = metadataHeaderRect.TakeFromLeft(headerHeight);
if (!isTag)
SdfIcons.DrawIcon(
foldoutTriangleRect.Padding(6),
metadataEntry.State.Expanded ? SdfIconType.CaretDownFill : SdfIconType.CaretRightFill,
new Color(0.443f, 0.443f, 0.443f));
var iconRect = metadataHeaderRect.TakeFromLeft(headerHeight);
SdfIcons.DrawIcon(
iconRect.Padding(0, 4, 4, 4),
isTag ? SdfIconType.TagFill : SdfIconType.GeoAltFill,
new Color(0.933f, 0.933f, 0.933f));
var contextMenuIconRect = metadataHeaderRect.TakeFromRight(headerHeight);
SdfIcons.DrawIcon(contextMenuIconRect.Padding(7), SdfIconType.ThreeDotsVertical);
var niceName = ObjectNames.NicifyVariableName(metadataEntry.ValueEntry.TypeOfValue.GetNiceName());
if (metadataEntry.Info.TypeOfOwner == typeof(Comment))
{
var headerLabelWidth = EditorStyles.label.CalcWidth(niceName) + 1;
var comment = ((string)metadataEntry.ValueEntry.WeakSmartValue).Split('\n')[0];
var hintRect = metadataHeaderRect.AlignRight(metadataHeaderRect.width - headerLabelWidth - 10);
if (SirenixGUIStyles.RightAlignedGreyMiniLabel.CalcWidth(comment) > hintRect.width)
{
if (hintRect.width < 30)
{
comment = "";
}
else
{
GUI.Label(hintRect.TakeFromLeft(11), "...", SirenixGUIStyles.RightAlignedGreyMiniLabel);
}
}
GUI.Label(hintRect, comment, SirenixGUIStyles.RightAlignedGreyMiniLabel);
}
else if (metadataEntry.Info.TypeOfOwner == typeof(PreloadAssetTableMetadata))
{
var headerLabelWidth = EditorStyles.label.CalcWidth(niceName) + 1;
var preloadAssetLabel = ObjectNames.NicifyVariableName(metadataEntry.ValueEntry.WeakSmartValue.ToString());
var hintRect = metadataHeaderRect.AlignRight(metadataHeaderRect.width - headerLabelWidth - 10);
if (SirenixGUIStyles.RightAlignedGreyMiniLabel.CalcWidth(preloadAssetLabel) > hintRect.width)
{
if (hintRect.width < 30)
{
preloadAssetLabel = "";
}
else
{
GUI.Label(hintRect.TakeFromLeft(11), "...", SirenixGUIStyles.RightAlignedGreyMiniLabel);
}
}
GUI.Label(hintRect, preloadAssetLabel, SirenixGUIStyles.RightAlignedGreyMiniLabel);
}
else if (metadataEntry.Info.TypeOfOwner == typeof(PlatformOverride))
{
var platformOverrides = metadataEntry.Children;
var headerLabelWidth = EditorStyles.label.CalcWidth(niceName) + 1;
var hintRect = metadataHeaderRect.AlignRight(metadataHeaderRect.width - headerLabelWidth - 10);
this.stringBuilder.Clear();
for (var j = 0; j < platformOverrides.Count; j++)
{
var platformOverrideInspectorProperty = platformOverrides[j];
var platform = (RuntimePlatform)platformOverrideInspectorProperty.Children[0].ValueEntry.WeakSmartValue;
this.stringBuilder.Append(platform);
if (j != platformOverrides.Count - 1)
{
this.stringBuilder.Append(", ");
}
}
var platforms = this.stringBuilder.ToString();
if (SirenixGUIStyles.RightAlignedGreyMiniLabel.CalcWidth(platforms) > hintRect.width)
{
if (hintRect.width < 30)
{
GUI.Label(hintRect, $"[{platformOverrides.Count}]", SirenixGUIStyles.RightAlignedGreyMiniLabel);
platforms = "";
}
else if (platformOverrides.Count > 1)
{
GUI.Label(hintRect.TakeFromLeft(11), "...", SirenixGUIStyles.RightAlignedGreyMiniLabel);
}
}
GUI.Label(hintRect, platforms, SirenixGUIStyles.RightAlignedGreyMiniLabel);
}
GUI.Label(metadataHeaderRect, niceName);
var evt = Event.current;
if (evt.OnMouseDown(contextMenuIconRect, 0, false))
genericMenu.ShowAsContext();
else if (evt.OnMouseDown(unchangedMetadataHeaderRect, 1, false)) genericMenu.ShowAsContext();
if (metadataEntry.ValueEntry.TypeOfValue == typeof(ExcludeEntryFromExport)) continue;
if (evt.OnMouseDown(unchangedMetadataHeaderRect, 0, false))
{
if (holdingControl)
{
var newState = !metadataEntry.State.Expanded;
foreach (var entry in filteredMetadataEntries)
{
entry.State.Expanded = newState;
}
}
else if (holdingAlt)
{
var newState = !metadataEntry.State.Expanded;
foreach (var entry in filteredMetadataEntries.Where(m => m.ValueEntry.TypeOfValue == this.lastMetadataTypeUnderCursor))
{
entry.State.Expanded = newState;
}
}
else
{
metadataEntry.State.Expanded = !metadataEntry.State.Expanded;
}
GUIHelper.RemoveFocusControl();
}
if (SirenixEditorGUI.BeginFadeGroup(metadataEntry, metadataEntry.State.Expanded))
{
EditorGUI.DrawRect(unchangedMetadataHeaderRect.AlignBottom(1),
new Color(0.188f, 0.188f, 0.188f));
var contentRect = EditorGUILayout.BeginVertical();
if (metadataEntry.ValueEntry.TypeOfValue == typeof(Comment))
{
EditorGUILayout.BeginVertical(this.ContentPadding);
foreach (var child in metadataEntry.Children)
{
child.Draw(null);
}
EditorGUILayout.EndVertical();
}
else if (metadataEntry.ValueEntry.TypeOfValue == typeof(PreloadAssetTableMetadata))
{
EditorGUILayout.BeginVertical(this.ContentPadding);
var preloadAssetTableMetadata = (PreloadAssetTableMetadata)metadataEntry.ValueEntry.WeakSmartValue;
var btnRect = EditorGUILayout.GetControlRect(false);
var leftRect = btnRect.Split(0, 2);
var mouseOverLeft = Event.current.IsMouseOver(leftRect);
var leftActive = mouseOverLeft && Event.current.type == EventType.MouseDown && Event.current.button == 0;
var leftOn = preloadAssetTableMetadata.Behaviour == PreloadAssetTableMetadata.PreloadBehaviour.NoPreload;
var rightRect = btnRect.Split(1, 2);
var mouseOverRight = Event.current.IsMouseOver(rightRect);
var rightActive = mouseOverRight && Event.current.type == EventType.MouseDown && Event.current.button == 0;
var rightOn = preloadAssetTableMetadata.Behaviour == PreloadAssetTableMetadata.PreloadBehaviour.PreloadAll;
if (Event.current.type == EventType.Repaint)
{
SirenixGUIStyles.ButtonLeft.Draw(leftRect, "No Preload", mouseOverLeft, leftActive, leftOn, false);
SirenixGUIStyles.ButtonRight.Draw(rightRect, "Preload All", mouseOverRight, rightActive, rightOn, false);
}
if (Event.current.OnMouseDown(leftRect, 0, false))
{
preloadAssetTableMetadata.Behaviour = PreloadAssetTableMetadata.PreloadBehaviour.NoPreload;
}
else if (Event.current.OnMouseDown(rightRect, 0, false))
{
preloadAssetTableMetadata.Behaviour = PreloadAssetTableMetadata.PreloadBehaviour.PreloadAll;
}
EditorGUILayout.EndVertical();
}
else if (metadataEntry.ValueEntry.TypeOfValue == typeof(PlatformOverride))
{
this.DrawPlatformOverride(metadataEntry);
}
else
{
EditorGUILayout.BeginVertical(this.ContentPadding);
foreach (var child in metadataEntry.Children)
{
child.Draw();
}
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndVertical();
}
SirenixEditorGUI.EndFadeGroup();
}
}
var measureRect = GUIHelper.EndLayoutMeasuring();
EditorGUILayout.EndScrollView();
GUILayout.EndArea();
var pinAddMetadataButton = measureRect.height > metadataViewHeight;
rect.TakeFromTop(pinAddMetadataButton ? metadataViewHeight : measureRect.height);
if (metadataEntries.Count > 1)
{
EditorGUI.DrawRect(rect.TakeFromTop(1), SirenixGUIStyles.BorderColor);
}
var addMetadataRect = rect.AlignTop(addButtonAreaHeight).AlignCenter(Mathf.Min(200, rect.width - 16)).VerticalPadding(8);
if (GUI.Button(addMetadataRect, "Add Metadata"))
{
openMetadataSelector();
}
// Draw the shadow that appears under the search bar while scrolling through the metadata.
GUI.DrawTexture(
searchAreaRect.AlignBottom(10).AddY(10),
OdinLocalizationGUITextures.TopToBottomFade,
ScaleMode.StretchToFill,
true,
1.0f,
new Color(0, 0, 0, Mathf.InverseLerp(0f, 30f, this.scrollPosition.y) * 0.6f),
0,
0);
if (measureRect.height > metadataViewHeight)
{
var pos = this.scrollPosition.y + metadataViewHeight;
// Draw the shadow that appears at the bottom of the metadata while scrolling through the metadata
GUI.DrawTexture(
rect.AlignTop(10).SubY(10),
OdinLocalizationGUITextures.BottomToTopFade,
ScaleMode.StretchToFill,
true,
1.0f,
new Color(0, 0, 0, Mathf.InverseLerp(measureRect.height, measureRect.height - 30, pos) * 0.6f),
0,
0);
}
return measureRect.width;
}
private void DrawPlatformOverride(InspectorProperty metadataEntry)
{
var platformOverride = (PlatformOverride)metadataEntry.ValueEntry.WeakSmartValue;
var platformOverrideDatas = (IList)metadataEntry.Children["m_PlatformOverrides"]?.ValueEntry.WeakSmartValue;
var platformOverrideDataInspectorProperties = metadataEntry.Children["m_PlatformOverrides"]?.Children;
if (platformOverrideDatas == null)
{
return;
}
if (platformOverrideDatas.Count == 0)
{
platformOverride.AddPlatformOverride(RuntimePlatform.WindowsPlayer, null, null, EntryOverrideType.None);
}
for (var i = 0; i < platformOverrideDataInspectorProperties.Count; i++)
{
var platformOverrideDataInspectorProperty = platformOverrideDataInspectorProperties[i];
var backgroundRect = EditorGUILayout.BeginVertical(this.PlatformOverridePadding);
var backgroundColor = i % 2 == 0 ? SirenixGUIStyles.ListItemColorEven : SirenixGUIStyles.ListItemColorOdd;
EditorGUI.DrawRect(backgroundRect, backgroundColor);
platformOverrideDataInspectorProperty.Children["platform"].Draw();
platformOverrideDataInspectorProperty.Children["entryOverrideType"].Draw();
var entryOverrideType = (EntryOverrideType)platformOverrideDataInspectorProperty.Children
.FirstOrDefault(c => c.Info.TypeOfValue == typeof(EntryOverrideType))?.ValueEntry.WeakSmartValue;
var entryOverrideRect = GUILayoutUtility.GetRect(0, EditorGUIUtility.singleLineHeight, GUILayoutOptions.ExpandWidth(true).ExpandHeight(false));
switch (entryOverrideType)
{
case EntryOverrideType.Table:
{
var tableReference = (TableReference)platformOverrideDataInspectorProperty.Children.FirstOrDefault(c => c.Info.TypeOfValue == typeof(TableReference))?.ValueEntry.WeakSmartValue;
this.DoTableGUI(entryOverrideRect, tableReference, platformOverrideDataInspectorProperty);
break;
}
case EntryOverrideType.Entry:
{
var tableReference = (TableReference)platformOverrideDataInspectorProperty.Children.FirstOrDefault(c => c.Info.TypeOfValue == typeof(TableReference))
?.ValueEntry.WeakSmartValue;
var tableEntryReference = (TableEntryReference)platformOverrideDataInspectorProperty.Children.FirstOrDefault(c => c.Info.TypeOfValue == typeof(TableEntryReference))?.ValueEntry.WeakSmartValue;
this.DoTableAndEntryGUI(entryOverrideRect, tableReference, tableEntryReference, platformOverrideDataInspectorProperty, false);
break;
}
case EntryOverrideType.TableAndEntry:
{
var tableReference = (TableReference)platformOverrideDataInspectorProperty.Children.FirstOrDefault(c => c.Info.TypeOfValue == typeof(TableReference))?.ValueEntry.WeakSmartValue;
var tableEntryReference = (TableEntryReference)platformOverrideDataInspectorProperty.Children.FirstOrDefault(c => c.Info.TypeOfValue == typeof(TableEntryReference))?.ValueEntry.WeakSmartValue;
this.DoTableAndEntryGUI(entryOverrideRect, tableReference, tableEntryReference, platformOverrideDataInspectorProperty, true);
break;
}
}
EditorGUILayout.EndVertical();
var xIconRect = backgroundRect.AlignRight(10).SubX(4).AlignCenterY(10);
var mouseOverX = Event.current.IsMouseOver(xIconRect);
SdfIcons.DrawIcon(xIconRect, SdfIconType.X, mouseOverX ? Color.white : EditorStyles.label.normal.textColor);
if (Event.current.OnMouseDown(xIconRect, 0, false))
{
var platform = (RuntimePlatform)platformOverrideDataInspectorProperty.Children[0].ValueEntry.WeakSmartValue;
platformOverride.RemovePlatformOverride(platform);
}
}
var addButtonRect = GUILayoutUtility.GetRect(0, 40, GUILayoutOptions.ExpandWidth().ExpandHeight(false));
EditorGUI.DrawRect(addButtonRect.AlignTop(1), new Color(0.188f, 0.188f, 0.188f));
if (GUI.Button(addButtonRect.Padding(8), "Add Platform Override"))
{
var selector = new EnumSelector<RuntimePlatform>();
selector.SelectionConfirmed += platforms =>
{
var platform = platforms.FirstOrDefault();
platformOverride.AddPlatformOverride(platform, null, null, EntryOverrideType.None);
};
selector.ShowInPopup();
}
}
private void OpenMetadataSelector(LocalizationTableCollection localizationTableCollection)
{
this.OpenMetadataSelector(localizationTableCollection.SharedData.Metadata.MetadataEntries, MetadataType.SharedTableData);
}
private void OpenMetadataSelector(LocalizationTable localizationTable)
{
switch (localizationTable)
{
case StringTable _:
this.OpenMetadataSelector(localizationTable.MetadataEntries, MetadataType.StringTable);
break;
case AssetTable _:
this.OpenMetadataSelector(localizationTable.MetadataEntries, MetadataType.AssetTable);
break;
}
}
private void OpenMetadataSelector(SharedTableData.SharedTableEntry sharedTableEntry)
{
switch (this.localizationTableCollection)
{
case StringTableCollection _:
this.OpenMetadataSelector(sharedTableEntry.Metadata.MetadataEntries, MetadataType.StringTableEntry, MetadataType.SharedStringTableEntry);
break;
case AssetTableCollection _:
this.OpenMetadataSelector(sharedTableEntry.Metadata.MetadataEntries, MetadataType.AssetTableEntry, MetadataType.SharedAssetTableEntry);
break;
}
}
private void OpenMetadataSelector(TableEntry tableEntry)
{
switch (tableEntry)
{
case StringTableEntry _:
this.OpenMetadataSelector(tableEntry.MetadataEntries, MetadataType.StringTableEntry);
break;
case AssetTableEntry _:
this.OpenMetadataSelector(tableEntry.MetadataEntries, MetadataType.AssetTableEntry);
break;
}
}
private void OpenMetadataSelector(IList<IMetadata> existingMetadata, params MetadataType[] allowedTypes)
{
var existingMetadataTypes = existingMetadata.Select(m => m?.GetType()).ToList();
var metadataTypes = TypeCache.GetTypesDerivedFrom<IMetadata>();
var validMetadataTypes = new List<Type>();
foreach (var metadataType in metadataTypes)
{
var metadataAttribute = metadataType.GetCustomAttribute<MetadataAttribute>();
if (metadataAttribute != null && allowedTypes.Any(allowedType => metadataAttribute.AllowedTypes.HasFlag(allowedType)))
{
if (existingMetadataTypes.Contains(metadataType) && !OdinLocalizationMetadataRegistry.MetadataAllowsMultiple.ContainsKey(metadataType)) continue;
validMetadataTypes.Add(metadataType);
}
}
var selector = new GenericSelector<Type>("", validMetadataTypes, false, type => ObjectNames.NicifyVariableName(type.Name));
foreach (var item in selector.SelectionTree.MenuItems)
{
item.SdfIcon = SdfIconType.Braces;
}
selector.SelectionConfirmed += types =>
{
var selectedType = types.FirstOrDefault();
if (selectedType == null)
{
return;
}
var metadata = (IMetadata)Activator.CreateInstance(selectedType);
this.toBeAdded = metadata;
};
selector.EnableSingleClickToSelect();
selector.ShowInPopup();
}
public class TableReferenceSelector : OdinSelector<LocalizationTableCollection>
{
private Type tableType;
public TableReferenceSelector(Type tableType)
{
this.tableType = tableType;
}
protected override void BuildSelectionTree(OdinMenuTree tree)
{
tree.Config.SelectMenuItemsOnMouseDown = true;
tree.Selection.SupportsMultiSelect = false;
var collectionGUIDs = AssetDatabase.FindAssets($"t:{nameof(LocalizationTableCollection)}");
foreach (var guid in collectionGUIDs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var collection = AssetDatabase.LoadAssetAtPath<LocalizationTableCollection>(path);
if (collection is null) continue;
if (collection.Tables.Count < 1) continue;
if (collection.Tables[0].asset.GetType() != this.tableType) continue;
var collectionItem = new OdinMenuItem(tree, collection.name, collection)
{
SdfIcon = SdfIconType.Table
};
tree.MenuItems.Add(collectionItem);
}
}
}
public class TableEntryReferenceSelector : OdinSelector<TableEntryReferenceSelector.TableEntry>
{
private Type tableType;
public struct TableEntry
{
public LocalizationTableCollection Collection;
public SharedTableData.SharedTableEntry SharedEntry;
}
public TableEntryReferenceSelector(Type tableType)
{
this.tableType = tableType;
}
protected override void BuildSelectionTree(OdinMenuTree tree)
{
tree.Config.SelectMenuItemsOnMouseDown = true;
tree.Selection.SupportsMultiSelect = false;
var collectionGUIDs = AssetDatabase.FindAssets($"t:{nameof(LocalizationTableCollection)}");
foreach (var guid in collectionGUIDs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var collection = AssetDatabase.LoadAssetAtPath<LocalizationTableCollection>(path);
if (collection is null) continue;
if (collection.Tables.Count < 1) continue;
if (collection.Tables[0].asset.GetType() != this.tableType) continue;
var collectionItem = new OdinMenuItem(tree, collection.name, collection)
{
SdfIcon = SdfIconType.Table
};
List<SharedTableData.SharedTableEntry> entries = collection.SharedData.Entries;
for (var j = 0; j < entries.Count; j++)
{
SharedTableData.SharedTableEntry entry = entries[j];
var tableEntry = new TableEntry {Collection = collection, SharedEntry = entry};
var entryItem = new OdinMenuItem(tree, entry.Key, tableEntry)
{
SdfIcon = SdfIconType.KeyFill
};
collectionItem.ChildMenuItems.Add(entryItem);
}
collectionItem.IsSelectable = false;
tree.MenuItems.Add(collectionItem);
}
}
}
private void DoTableGUI(Rect rect, TableReference tableReference, InspectorProperty platformOverrideData)
{
var dropDownPosition = EditorGUI.PrefixLabel(rect, new GUIContent("Table Collection"));
var label = tableReference.TableCollectionName;
if (EditorGUI.DropdownButton(dropDownPosition, new GUIContent(label), FocusType.Passive))
{
TableReferenceSelector tableReferenceSelector;
tableReferenceSelector = this.localizationTableCollection.GetType() == typeof(AssetTableCollection)
? new TableReferenceSelector(typeof(AssetTable))
: new TableReferenceSelector(typeof(StringTable));
tableReferenceSelector.GetCurrentSelection();
tableReferenceSelector.SelectionConfirmed += selections =>
{
var selection = selections.FirstOrDefault();
var platformOverrideType = typeof(PlatformOverride);
var platformOverrideDataType = platformOverrideType.GetNestedType("PlatformOverrideData", BindingFlags.NonPublic);
platformOverrideDataType
.GetField("tableReference")
.SetValue(platformOverrideData.ValueEntry.WeakSmartValue, selection.TableCollectionNameReference);
platformOverrideDataType
.GetField("tableEntryReference")
.SetValue(platformOverrideData.ValueEntry.WeakSmartValue, (TableEntryReference) string.Empty);
};
tableReferenceSelector.ShowInPopup();
}
}
private void DoTableAndEntryGUI(Rect rect,
TableReference tableReference,
TableEntryReference tableEntryReference,
InspectorProperty platformOverrideData,
bool displayTableName)
{
var dropDownPosition = EditorGUI.PrefixLabel(rect, new GUIContent("Reference"));
var entryLabel = tableEntryReference.ReferenceType != TableEntryReference.Type.Empty
? tableEntryReference.ResolveKeyName(this.localizationTableCollection.SharedData)
: null;
string label;
if (displayTableName)
{
var tableLabel = tableReference.TableCollectionName ?? "";
label = string.IsNullOrEmpty(tableLabel) || string.IsNullOrEmpty(entryLabel) ? "None" : $"{tableLabel}/{entryLabel}";
}
else
{
label = string.IsNullOrWhiteSpace(entryLabel) ? "None" : $"{entryLabel}";
}
if (EditorGUI.DropdownButton(dropDownPosition, new GUIContent(label), FocusType.Passive))
{
Type tableType = this.localizationTableCollection.GetType() == typeof(AssetTableCollection)
? typeof(AssetTable)
: typeof(StringTable);
TableEntryReferenceSelector tableEntryReferenceSelector = new TableEntryReferenceSelector(tableType);
tableEntryReferenceSelector.SelectionConfirmed += selections =>
{
var selection = selections.FirstOrDefault();
var platformOverrideType = typeof(PlatformOverride);
var platformOverrideDataType = platformOverrideType.GetNestedType("PlatformOverrideData", BindingFlags.NonPublic);
platformOverrideDataType
.GetField("tableReference")
.SetValue(platformOverrideData.ValueEntry.WeakSmartValue, selection.Collection.TableCollectionNameReference);
platformOverrideDataType
.GetField("tableEntryReference")
.SetValue(platformOverrideData.ValueEntry.WeakSmartValue, (TableEntryReference) selection.SharedEntry.Key);
};
tableEntryReferenceSelector.ShowInPopup();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1490cea564e237341b75e0cdba1f374a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bc0d9dcd4d4f4e3094085c752e185ef8
timeCreated: 1704811181

View File

@@ -0,0 +1,196 @@
//-----------------------------------------------------------------------
// <copyright file="OdinGUITable.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
#define USING_WIDTH_NON_PERCENT
//#undef USING_WIDTH_NON_PERCENT
using System;
using Sirenix.OdinInspector.Editor.Internal;
using Sirenix.Utilities.Editor;
using UnityEditor;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Tables;
using UnityEngine.Serialization;
namespace Sirenix.OdinInspector.Modules.Localization.Editor
{
public class OdinGUITable<TTable> : IComparable<OdinGUITable<TTable>> where TTable : LocalizationTable
{
public const string KEY_DISPLAY_NAME = "Key";
public enum GUITableType
{
Default,
Key
}
public float Width
{
get => this._width;
set
{
if (value < OdinLocalizationConstants.MIN_COLUMN_WIDTH)
{
this._width = OdinLocalizationConstants.MIN_COLUMN_WIDTH;
return;
}
this._width = Mathf.Round(value);
}
}
public bool IsDraggingSlider = false;
public bool IsVisible;
public string DisplayName;
public GUITableType Type;
[FormerlySerializedAs("Table")]
public TTable Asset;
public bool IsPinned;
private float _width;
#if USING_WIDTH_NON_PERCENT
//public float VisualWidth;
#else
internal float WidthPercentage;
#endif
#if USING_WIDTH_NON_PERCENT
public static OdinGUITable<TTable> CreateKeyTable()
#else
public static OdinGUITable<TTable> CreateKeyTable(float widthPercent)
#endif
{
return new OdinGUITable<TTable>
{
IsVisible = true,
DisplayName = KEY_DISPLAY_NAME,
Type = GUITableType.Key,
Asset = null,
IsPinned = false,
Width = OdinLocalizationConstants.DEFAULT_COLUMN_WIDTH,
#if USING_WIDTH_NON_PERCENT
// VisualWidth = OdinLocalizationConstants.DEFAULT_COLUMN_WIDTH
#else
WidthPercentage = widthPercent
#endif
};
}
#if USING_WIDTH_NON_PERCENT
public static OdinGUITable<TTable> CreateTable(TTable table, Locale locale)
#else
public static OdinGUITable<TTable> CreateTable(TTable table, float widthPercent)
#endif
{
return new OdinGUITable<TTable>
{
IsVisible = true,
DisplayName = locale.LocaleName,
Type = GUITableType.Default,
Asset = table,
IsPinned = false,
Width = OdinLocalizationConstants.DEFAULT_COLUMN_WIDTH,
#if USING_WIDTH_NON_PERCENT
// VisualWidth = OdinLocalizationConstants.DEFAULT_COLUMN_WIDTH
#else
WidthPercentage = widthPercent
#endif
};
}
public int CompareTo(OdinGUITable<TTable> other)
{
if (other == null)
{
return -1;
}
if (this.IsVisible && !other.IsVisible)
{
return -1;
}
if (!this.IsVisible && other.IsVisible)
{
return 1;
}
if (this.IsPinned && !other.IsPinned)
{
return -1;
}
if (!this.IsPinned && other.IsPinned)
{
return 1;
}
if (this.DisplayName == KEY_DISPLAY_NAME && other.DisplayName == KEY_DISPLAY_NAME)
{
return 0;
}
if (other.DisplayName == KEY_DISPLAY_NAME)
{
return 1;
}
if (this.DisplayName == KEY_DISPLAY_NAME)
{
return -1;
}
return this.Asset.LocaleIdentifier.CompareTo(other.Asset.LocaleIdentifier);
}
public Vector2 HandleSlider(Rect position)
{
if (GUIUtility.hotControl == 0)
{
this.IsDraggingSlider = false;
}
if (!GUI.enabled)
{
return Vector2.zero;
}
EditorGUIUtility.AddCursorRect(position, MouseCursor.ResizeHorizontal);
if (GUI.enabled && Event.current.OnMouseDown(position, 0))
{
this.IsDraggingSlider = true;
SharedUniqueControlId.SetActive();
EditorGUIUtility.SetWantsMouseJumping(1);
Event.current.Use();
}
else if (SharedUniqueControlId.IsActive && this.IsDraggingSlider)
{
if (Event.current.type == EventType.MouseDrag)
{
Event.current.Use();
GUI.changed = true;
return Event.current.delta;
}
if (Event.current.type != EventType.MouseUp)
{
return Vector2.zero;
}
this.IsDraggingSlider = false;
SharedUniqueControlId.SetInactive();
EditorGUIUtility.SetWantsMouseJumping(0);
Event.current.Use();
}
return Vector2.zero;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c6172b1a79444464bfac84d14344febf
timeCreated: 1706411334

View File

@@ -0,0 +1,483 @@
//-----------------------------------------------------------------------
// <copyright file="OdinGUITableCollection.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
#define USING_WIDTH_NON_PERCENT
//#undef USING_WIDTH_NON_PERCENT
using System;
using System.Collections.Generic;
using Sirenix.OdinInspector.Editor.Internal;
using UnityEditor;
using UnityEngine.Localization.Tables;
namespace Sirenix.OdinInspector.Modules.Localization.Editor
{
public class OdinGUITableCollection<TTable> : List<OdinGUITable<TTable>> where TTable : LocalizationTable
{
public readonly struct VisibleTables
{
public readonly int Offset;
public readonly int Length;
public readonly int PinnedLength;
public readonly float StartOffset;
private readonly OdinGUITableCollection<TTable> collection;
public VisibleTables(OdinGUITableCollection<TTable> collection, int offset, int length, int pinnedLength, float startOffset)
{
this.collection = collection;
this.Offset = offset;
this.Length = length + pinnedLength;
this.PinnedLength = pinnedLength;
this.StartOffset = startOffset;
}
public OdinGUITable<TTable> this[int index] =>
index >= this.PinnedLength ? this.collection[this.Offset + index - this.PinnedLength] : this.collection[index];
}
//public VisibleTables CurrentVisibleTables;
public HashSet<OdinGUITable<TTable>> TablesWithinVisibleBounds = new HashSet<OdinGUITable<TTable>>();
public OdinGUITableCollection(int capacity) : base(capacity) { }
#if USING_WIDTH_NON_PERCENT
public void AddKeyTable()
{
for (var i = 0; i < this.Count; i++)
{
if (this[i].Type == OdinGUITable<TTable>.GUITableType.Key)
{
return;
}
}
this.Add(OdinGUITable<TTable>.CreateKeyTable());
}
#else
public void AddKeyTable(float widthPercent)
{
for (var i = 0; i < this.Count; i++)
{
if (this[i].Type == OdinGUITable<TTable>.GUITableType.Key)
{
return;
}
}
this.Add(OdinGUITable<TTable>.CreateKeyTable(widthPercent));
}
#endif
#if USING_WIDTH_NON_PERCENT
public void UpdateVisibleTables(OdinGUIScrollView view, float pinnedWidth)
{
#if true
this.TablesWithinVisibleBounds.Clear();
var offset = 0;
var currentVisibleWidth = 0.0f;
var xMin = 0.0f;
var xMax = 0.0f;
for (var i = 0; i < this.Count; i++)
{
OdinGUITable<TTable> table = this[i];
if (!table.IsVisible)
{
continue;
}
if (table.IsPinned)
{
this.TablesWithinVisibleBounds.Add(table);
continue;
}
xMax += table.Width;
bool isVisible = view.Position.x >= xMin && view.Position.x <= xMax;
if (!isVisible)
{
xMin = xMax;
continue;
}
offset = i;
currentVisibleWidth = xMax - view.Position.x;
this.TablesWithinVisibleBounds.Add(table);
break;
}
float width = view.Bounds.width - pinnedWidth;
for (int i = offset + 1; i < this.Count; i++)
{
OdinGUITable<TTable> table = this[i];
if (!table.IsVisible)
{
break;
}
if (currentVisibleWidth >= width)
{
this.TablesWithinVisibleBounds.Add(table);
break;
}
this.TablesWithinVisibleBounds.Add(table);
currentVisibleWidth += this[i].Width;
}
#else
if (this.GetVisibleCount() < 1)
{
this.CurrentVisibleTables = new VisibleTables(this, 0, 0, 0, 0.0f);
return;
}
const int LENGTH_NOT_SET = -1;
var offset = 0;
int length = LENGTH_NOT_SET;
var currentVisibleWidth = 0.0f;
float xMin = 0.0f, xMax = 0.0f;
float positionX = view.Position.x;
float width = view.Bounds.width - pinnedWidth;
for (var i = 0; i < this.Count; i++)
{
OdinGUITable<TTable> table = this[i];
if (table.IsPinned)
{
continue;
}
if (!table.IsVisible)
{
break;
}
xMax += table.VisualWidth;
bool isVisible = positionX >= xMin && positionX <= xMax;
if (!isVisible)
{
xMin = xMax;
continue;
}
offset = i;
currentVisibleWidth = xMax - positionX;
break;
}
float startOffset = xMin;
for (int i = offset + 1; i < this.Count; i++)
{
if (!this[i].IsVisible)
{
length = i - offset;
break;
}
if (currentVisibleWidth >= width)
{
length = i - offset + 1;
break;
}
currentVisibleWidth += this[i].VisualWidth;
}
int pinnedCount = this.GetPinnedCount();
if (length == LENGTH_NOT_SET)
{
length = this.Count - offset;
}
this.CurrentVisibleTables = new VisibleTables(this, offset, length, pinnedCount, startOffset);
#endif
}
public float GetVisibleWidth()
{
var result = 0.0f;
for (var i = 0; i < this.Count; i++)
{
if (this[i].IsVisible)
{
result += this[i].Width;
}
}
return result;
}
public void ResizeBy(float factor)
{
for (var i = 0; i < this.Count; i++)
{
if (this[i].IsVisible)
{
this[i].Width *= factor;
}
}
}
#else
/// <summary> Calculates the <see cref="GUITable.Width"/> field on <see cref="GUITable"/> beforehand. </summary>
/// <param name="scrollView">The <see cref="OdinGUIScrollView"/> to perform calculations on.</param>
public void CalcWidths(OdinGUIScrollView scrollView)
{
float viewWidth = scrollView.ViewRect.width;
float visibleFactor = this.GetVisibleFactor();
for (var i = 0; i < this.Count; i++)
{
this[i].Width = viewWidth * (this[i].WidthPercentage * visibleFactor);
}
}
#endif
public int GetLastVisibleIndex()
{
for (int i = this.Count - 1; i >= 0; i--)
{
if (this[i].IsVisible)
{
return i;
}
}
return 0;
}
public int GetLastVisiblePinnedIndex()
{
for (int i = this.Count - 1; i >= 0; i--)
{
if (this[i].IsVisible && this[i].IsPinned)
{
return i;
}
}
return 0;
}
public int GetVisibleCount()
{
var result = 0;
for (var i = 0; i < this.Count; i++)
{
if (this[i].IsVisible)
{
result++;
}
}
return result;
}
public int GetHiddenCount()
{
var result = 0;
for (var i = 0; i < this.Count; i++)
{
if (!this[i].IsVisible)
{
result++;
}
}
return result;
}
public int GetPinnedCount()
{
var result = 0;
for (var i = 0; i < this.Count; i++)
{
if (this[i].IsVisible && this[i].IsPinned)
{
result++;
}
}
return result;
}
public OdinGUITable<TTable> GetNextVisible(int index)
{
for (int i = index + 1; i < this.Count; i++)
{
if (this[i].IsVisible)
{
return this[i];
}
}
return null;
}
public void UndoRecordCollection(SharedTableData sharedTableData, string name)
{
for (var i = 0; i < this.Count; i++)
{
switch (this[i].Type)
{
case OdinGUITable<TTable>.GUITableType.Default:
Undo.RecordObject(this[i].Asset, name);
break;
case OdinGUITable<TTable>.GUITableType.Key:
if (sharedTableData != null)
{
Undo.RecordObject(sharedTableData, name);
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
public void SetDirty(SharedTableData sharedTableData)
{
for (var i = 0; i < this.Count; i++)
{
switch (this[i].Type)
{
case OdinGUITable<TTable>.GUITableType.Default:
EditorUtility.SetDirty(this[i].Asset);
break;
case OdinGUITable<TTable>.GUITableType.Key:
if (sharedTableData != null)
{
EditorUtility.SetDirty(sharedTableData);
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
#if !USING_WIDTH_NON_PERCENT
private float GetVisibleFactor()
{
var visibleCount = 0;
var sum = 0.0f;
for (var i = 0; i < this.Count; i++)
{
if (!this[i].IsVisible)
{
continue;
}
sum += this[i].WidthPercentage;
visibleCount++;
}
float result = (float) this.Count / visibleCount;
sum *= result;
result *= 1.0f / sum;
return result;
}
#endif
public void ResizeToFit(float targetWidth)
{
var count = 0;
for (var i = 0; i < this.Count; i++)
{
if (this[i].IsPinned || !this[i].IsVisible)
{
continue;
}
count++;
}
if (count < 1)
{
return;
}
int averageSize = OdinLocalizationConstants.DEFAULT_COLUMN_WIDTH * count;
float scaleFactor = targetWidth / averageSize;
for (var i = 0; i < this.Count; i++)
{
if (!this[i].IsVisible || this[i].IsPinned)
{
continue;
}
this[i].Width = OdinLocalizationConstants.DEFAULT_COLUMN_WIDTH * scaleFactor;
}
}
public void ResizePinnedToFit(float targetWidth)
{
var currentTotalWidth = 0.0f;
for (var i = 0; i < this.Count; i++)
{
if (!this[i].IsPinned || !this[i].IsVisible)
{
continue;
}
currentTotalWidth += this[i].Width;
}
if (currentTotalWidth < 1.0f)
{
return;
}
float scaleFactor = targetWidth / currentTotalWidth;
for (var i = 0; i < this.Count; i++)
{
if (!this[i].IsVisible || !this[i].IsPinned)
{
continue;
}
this[i].Width *= scaleFactor;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3a739bb40d31496a9591094d81fd2d19
timeCreated: 1706411492

View File

@@ -0,0 +1,441 @@
//-----------------------------------------------------------------------
// <copyright file="OdinSharedEntryCollection.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector.Modules.Localization.Editor.Internal;
using Sirenix.Utilities.Editor;
using UnityEditor;
using UnityEditor.Localization;
using UnityEngine.Localization.Tables;
namespace Sirenix.OdinInspector.Modules.Localization.Editor
{
public class OdinSharedEntryCollection
{
private class StringComparer : IComparer<string>
{
public bool IsAscending = true;
public int Compare(string self, string other)
{
if (string.IsNullOrEmpty(self) && string.IsNullOrEmpty(other))
{
return 0;
}
if (string.IsNullOrEmpty(self))
{
return this.IsAscending ? 1 : -1;
}
if (string.IsNullOrEmpty(other))
{
return this.IsAscending ? -1 : 1;
}
return string.Compare(self, other, StringComparison.InvariantCulture);
}
}
public enum SortOrderState
{
Unsorted,
Ascending,
Descending
}
public bool IsSearching;
public bool IsSorted => this.CurrentSortOrderState != SortOrderState.Unsorted;
public int Length => this.Entries.Count;
private string _searchTerm = string.Empty;
private StringComparer stringComparer = new StringComparer();
public string SearchTerm
{
get => this._searchTerm;
private set
{
this._searchTerm = value;
this.IsSearching = !string.IsNullOrEmpty(value);
}
}
public List<SharedTableData.SharedTableEntry> Entries => this.IsSorted ? this.SortedEntries : this.Collection.SharedData.Entries;
public List<SharedTableData.SharedTableEntry> SortedEntries;
public readonly HashSet<SharedTableData.SharedTableEntry> FilteredEntries;
public readonly LocalizationTableCollection Collection;
public SortOrderState CurrentSortOrderState = SortOrderState.Unsorted;
public OdinSharedEntryCollection(LocalizationTableCollection collection)
{
this.Collection = collection;
this.FilteredEntries = new HashSet<SharedTableData.SharedTableEntry>();
}
public SharedTableData.SharedTableEntry this[int index] => this.Entries[index];
public bool IsVisible(SharedTableData.SharedTableEntry sharedEntry)
{
return !this.IsSearching || (this.IsSearching && this.FilteredEntries.Contains(sharedEntry));
}
public bool UpdateSearchTerm<TTable>(string value,
OdinGUITableCollection<TTable> tables,
LocalizationTableCollection collection,
bool forceUpdate = false) where TTable : LocalizationTable
{
if (this.SearchTerm == value && !forceUpdate)
{
return false;
}
this.SearchTerm = value;
if (string.IsNullOrEmpty(this.SearchTerm))
{
return true;
}
this.FilteredEntries.Clear();
for (var i = 0; i < tables.Count; i++)
{
OdinGUITable<TTable> table = tables[i];
switch (table.Type)
{
case OdinGUITable<TTable>.GUITableType.Default:
switch (table.Asset)
{
case AssetTable assetTable:
var assetCollection = collection as AssetTableCollection;
for (var j = 0; j < this.Length; j++)
{
SharedTableData.SharedTableEntry sharedEntry = this[j];
Type assetType = assetCollection.GetEntryAssetType(sharedEntry.Id);
UnityEngine.Object asset = OdinLocalizationAssetCache.Get(sharedEntry, assetTable, assetType);
if (asset == null)
{
continue;
}
if (FuzzySearch.Contains(this.SearchTerm, asset.name))
{
this.FilteredEntries.Add(sharedEntry);
}
}
break;
case StringTable stringTable:
for (var j = 0; j < this.Length; j++)
{
SharedTableData.SharedTableEntry sharedEntry = this[j];
StringTableEntry entry = stringTable.GetEntry(sharedEntry.Id);
if (entry is null || string.IsNullOrEmpty(entry.Value))
{
continue;
}
if (FuzzySearch.Contains(this.SearchTerm, entry.Value))
{
this.FilteredEntries.Add(sharedEntry);
}
}
break;
}
break;
case OdinGUITable<TTable>.GUITableType.Key:
for (var j = 0; j < this.Entries.Count; j++)
{
if (FuzzySearch.Contains(this.SearchTerm, this.Entries[j].Key))
{
this.FilteredEntries.Add(this.Entries[j]);
}
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
return true;
}
public void SortByKeys(bool preserveCurrentOrder)
{
switch (this.CurrentSortOrderState)
{
case SortOrderState.Ascending:
this.stringComparer.IsAscending = true;
if (preserveCurrentOrder)
{
List<SharedTableData.SharedTableEntry> result = this.Collection.SharedData.Entries.OrderBy(entry => entry.Key, this.stringComparer)
.ThenBy(this.GetOrderIndex)
.ToList();
this.SortedEntries = result;
}
else
{
this.SortedEntries = this.Collection.SharedData.Entries.OrderBy(entry => entry.Key, this.stringComparer).ToList();
}
return;
case SortOrderState.Descending:
this.stringComparer.IsAscending = false;
if (preserveCurrentOrder)
{
List<SharedTableData.SharedTableEntry> result = this.Collection.SharedData.Entries.OrderByDescending(entry => entry.Key, this.stringComparer)
.ThenBy(this.GetOrderIndex)
.ToList();
this.SortedEntries = result;
}
else
{
this.SortedEntries = this.Collection.SharedData.Entries.OrderByDescending(entry => entry.Key, this.stringComparer).ToList();
}
return;
}
}
public void SortByAssetTable(AssetTableCollection collection, AssetTable table, bool preserveCurrentOrder)
{
switch (this.CurrentSortOrderState)
{
case SortOrderState.Ascending:
this.stringComparer.IsAscending = true;
if (preserveCurrentOrder)
{
List<SharedTableData.SharedTableEntry> result = this.Collection.SharedData.Entries
.OrderBy(entry => GetAssetNameFromEntry(entry, table, collection), this.stringComparer)
.ThenBy(this.GetOrderIndex)
.ToList();
this.SortedEntries = result;
}
else
{
this.SortedEntries = this.Collection.SharedData.Entries.OrderBy(entry => GetAssetNameFromEntry(entry, table, collection), this.stringComparer)
.ToList();
}
return;
case SortOrderState.Descending:
this.stringComparer.IsAscending = false;
if (preserveCurrentOrder)
{
List<SharedTableData.SharedTableEntry> result = this.Collection.SharedData.Entries
.OrderByDescending(entry => GetAssetNameFromEntry(entry, table, collection),
this.stringComparer)
.ThenBy(this.GetOrderIndex)
.ToList();
this.SortedEntries = result;
}
else
{
this.SortedEntries = this.Entries.OrderByDescending(entry => GetAssetNameFromEntry(entry, table, collection), this.stringComparer).ToList();
}
return;
}
}
public void SortByStringTable(StringTable table, bool preserveCurrentOrder)
{
switch (this.CurrentSortOrderState)
{
case SortOrderState.Ascending:
this.stringComparer.IsAscending = true;
if (preserveCurrentOrder)
{
List<SharedTableData.SharedTableEntry> result = this.Collection.SharedData.Entries
.OrderBy(entry => GetStringFromEntry(entry, table), this.stringComparer)
.ThenBy(this.GetOrderIndex)
.ToList();
this.SortedEntries = result;
}
else
{
this.SortedEntries = this.Collection.SharedData.Entries.OrderBy(entry => GetStringFromEntry(entry, table), this.stringComparer).ToList();
}
return;
case SortOrderState.Descending:
this.stringComparer.IsAscending = false;
if (preserveCurrentOrder)
{
List<SharedTableData.SharedTableEntry> result = this.Collection.SharedData.Entries
.OrderByDescending(entry => GetStringFromEntry(entry, table), this.stringComparer)
.ThenBy(this.GetOrderIndex)
.ToList();
this.SortedEntries = result;
}
else
{
this.SortedEntries = this.Entries.OrderByDescending(entry => GetStringFromEntry(entry, table), this.stringComparer).ToList();
}
return;
}
}
private static string GetStringFromEntry(SharedTableData.SharedTableEntry sharedEntry, StringTable table)
{
StringTableEntry entry = table.GetEntry(sharedEntry.Id);
return entry?.Value;
}
private static string GetAssetNameFromEntry(SharedTableData.SharedTableEntry sharedEntry, AssetTable table, AssetTableCollection collection)
{
AssetTableEntry entry = table.GetEntry(sharedEntry.Id);
if (entry == null || entry.IsEmpty)
{
return null;
}
Type type = collection.GetEntryAssetType(sharedEntry.Id);
UnityEngine.Object asset = OdinLocalizationAssetCache.Get(entry.Guid, type);
return asset == null ? null : asset.name;
}
public void MoveEntry(int from, int to)
{
if (from < 0 || from >= this.Entries.Count)
{
return;
}
if (to < 0 || to > this.Entries.Count)
{
return;
}
if (from == to)
{
return;
}
SharedTableData.SharedTableEntry fromEntry = this.Collection.SharedData.Entries[from];
if (to > from)
{
to -= 1;
}
//to = afterTo ? to + 1 : to;
this.Collection.SharedData.Entries.RemoveAt(from);
this.Collection.SharedData.Entries.Insert(to, fromEntry);
OdinLocalizationEvents.RaiseTableEntryModified(this.Collection.SharedData.Entries[from]);
OdinLocalizationEvents.RaiseTableEntryModified(this.Collection.SharedData.Entries[to]);
EditorUtility.SetDirty(this.Collection.SharedData);
}
public void GotoNextSortOrderState()
{
switch (this.CurrentSortOrderState)
{
case SortOrderState.Unsorted:
this.CurrentSortOrderState = SortOrderState.Ascending;
break;
case SortOrderState.Ascending:
this.CurrentSortOrderState = SortOrderState.Descending;
break;
case SortOrderState.Descending:
this.CurrentSortOrderState = SortOrderState.Unsorted;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public int GetIndex(SharedTableData.SharedTableEntry sharedEntry)
{
for (var i = 0; i < this.Length; i++)
{
if (this[i].Id == sharedEntry.Id)
{
return i;
}
}
return -1;
}
public int GetOrderIndex(SharedTableData.SharedTableEntry sharedEntry)
{
if (this.IsSorted && this.SortedEntries.Count == this.Length)
{
for (var i = 0; i < this.SortedEntries.Count; i++)
{
if (this.SortedEntries[i].Id == sharedEntry.Id)
{
return i;
}
}
return -1;
}
for (var i = 0; i < this.Length; i++)
{
if (this[i].Id == sharedEntry.Id)
{
return i;
}
}
return -1;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a543e4ee8d6d4b629befbccf5aa7b5e6
timeCreated: 1706407967

View File

@@ -0,0 +1,449 @@
//-----------------------------------------------------------------------
// <copyright file="OdinStringTableCollectionEditor.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
#define USING_WIDTH_NON_PERCENT
using System;
using Sirenix.OdinInspector.Editor;
using Sirenix.OdinInspector.Editor.Internal;
using Sirenix.OdinInspector.Modules.Localization.Editor.Configs;
using Sirenix.OdinInspector.Modules.Localization.Editor.Internal;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using UnityEditor;
using UnityEditor.Localization;
using UnityEngine;
using UnityEngine.Localization.Tables;
namespace Sirenix.OdinInspector.Modules.Localization.Editor
{
public class OdinStringTableCollectionEditor : OdinTableCollectionEditor<StringTableCollection, StringTable, StringTableEntry>
{
private string currentSyntaxSource;
private string currentSyntaxHighlightedText;
private string currentSyntaxErrorMessage;
private Exception currentSyntaxException;
private bool currentSyntaxHasErrors;
public OdinStringTableCollectionEditor(StringTableCollection collection, OdinMenuEditorWindow relatedWindow,
OdinLocalizationEditorWindow.WindowState windowState) :
base(collection, relatedWindow, windowState) { }
protected override void OnInitialize()
{
for (var i = 0; i < this.SharedEntries.Length; i++)
{
SharedTableData.SharedTableEntry sharedEntry = this.SharedEntries[i];
this.MeasureEntry(sharedEntry);
}
//this.SharedEntries.OnSharedEntryAdded += (i, sharedEntry) => { this.MeasureEntry(sharedEntry); };
//this.SharedEntries.OnSharedEntryRemoved += (i, sharedEntry) => { this.SharedEntryHeights.Remove(sharedEntry.Id); };
this.OnTableEntryModified = sharedEntry =>
{
if (!this.Collection.SharedData.Contains(sharedEntry.Id))
{
return;
}
int index = this.SharedEntries.GetIndex(sharedEntry);
this.MeasureEntry(sharedEntry);
this.EntryScrollView.ReallocateRect(index, this.SharedEntryHeights[sharedEntry.Id], sharedEntry);
};
}
protected override void AllocateItems()
{
for (var i = 0; i < this.SharedEntries.Length; i++)
{
SharedTableData.SharedTableEntry sharedEntry = this.SharedEntries[i];
if (!this.SharedEntries.IsVisible(sharedEntry))
{
continue;
}
if (!this.SharedEntryHeights.ContainsKey(sharedEntry.Id))
{
this.MeasureEntry(sharedEntry);
}
this.EntryScrollView.AllocateRect(this.SharedEntryHeights[sharedEntry.Id], sharedEntry);
#if false
this.ControlIds[sharedEntry] = GUIUtility.GetControlID(FocusType.Keyboard);
for (var j = 0; j < this.GUITables.Count; j++)
{
OdinGUITable<StringTable> table = this.GUITables[j];
if (table.Type == OdinGUITable<StringTable>.GUITableType.Key)
{
continue;
}
StringTableEntry entry = table.Table.GetEntry(sharedEntry.Id);
if (entry is null)
{
table.Table.AddEntry(sharedEntry.Id, string.Empty);
entry = table.Table.GetEntry(sharedEntry.Id);
}
this.ControlIds[entry] = GUIUtility.GetControlID(FocusType.Keyboard);
}
#endif
}
}
protected override void DrawItems(ref OdinGUIScrollView.VisibleItems visibleItems)
{
this.MeasureVisibleEntries(ref visibleItems);
float scrollSpeed = OdinLocalizationConfig.Instance.scrollSpeed;
this.EntryScrollView.BeginScrollView(offset: new Vector2(this.PinnedWidth, OdinLocalizationConstants.COLUMN_HEIGHT),
addViewSize: new Vector2(-this.PinnedWidth, 0),
scrollSpeed: scrollSpeed);
{
this.DrawEntries(ref visibleItems, false);
}
this.EntryScrollView.EndScrollView();
this.EntryScrollView.BeginClip(offset: new Vector2(0.0f, OdinLocalizationConstants.COLUMN_HEIGHT), ignoreScrollX: true);
{
this.DrawEntries(ref visibleItems, true);
}
this.EntryScrollView.EndClip();
}
private void DrawEntries(ref OdinGUIScrollView.VisibleItems visibleItems, bool pinned)
{
for (var i = 0; i < visibleItems.Length; i++)
{
if (!visibleItems.HasAssociatedData(i))
{
continue;
}
int hint = visibleItems.Offset + i + this.ControlIdHint;
Rect position = visibleItems.GetRect(i);
var sharedEntry = visibleItems.GetAssociatedData<SharedTableData.SharedTableEntry>(i);
bool isEven = (visibleItems.Offset + i) % 2 == 0;
for (var j = 0; j < this.GUITables.Count; j++)
{
OdinGUITable<StringTable> table = this.GUITables[j];
if (!table.IsVisible)
{
continue;
}
if (table.IsPinned != pinned)
{
continue;
}
if (!this.GUITables.TablesWithinVisibleBounds.Contains(table))
{
GUIUtility.GetControlID(hint, FocusType.Keyboard);
position.TakeFromLeft(table.Width).Padding(OdinLocalizationConstants.ENTRY_PADDING);
continue;
}
#if USING_WIDTH_NON_PERCENT
Rect entryRect = position.TakeFromLeft(table.Width).Padding(OdinLocalizationConstants.ENTRY_PADDING);
#else
Rect entryRect = position.TakeFromLeft(table.Width).Padding(OdinLocalizationConstants.ENTRY_PADDING);
#endif
bool isCellPressed, isSelected;
switch (table.Type)
{
case OdinGUITable<StringTable>.GUITableType.Key:
isSelected = this.IsSharedEntrySelected(sharedEntry);
if (isSelected)
{
this.SelectionAnimFloat.Move(1 / 0.18f, Easing.InSine);
FancyColor start = FancyColor.Gray;
var end = OdinLocalizationGUI.Selected;
FancyColor.PushBlend(start.Lerp(end, this.SelectionAnimFloat), FancyColor.BlendMode.Overlay);
}
isCellPressed = DrawCell(entryRect, isEven);
this.DrawKey(entryRect, sharedEntry, GUIUtility.GetControlID(hint, FocusType.Keyboard));
if (isSelected)
{
FancyColor.PopBlend();
}
if (isCellPressed)
{
this.SelectSharedEntry(sharedEntry);
}
break;
case OdinGUITable<StringTable>.GUITableType.Default:
StringTableEntry entry = table.Asset.GetEntry(sharedEntry.Id);
isSelected = this.IsEntrySelected(entry);
if (isSelected)
{
this.SelectionAnimFloat.Move(1 / 0.18f, Easing.InSine);
FancyColor start = FancyColor.Gray;
FancyColor end = OdinLocalizationGUI.Selected;
if (entry.IsSmart && OdinLocalizationConfig.Instance.useSyntaxHighlighter)
{
if (this.currentSyntaxSource != entry.Value)
{
this.currentSyntaxHighlightedText = OdinLocalizationSyntaxHighlighter.HighlightAsRichText(entry.Value);
this.currentSyntaxErrorMessage = OdinLocalizationSyntaxHighlighter.GetErrorMessage(entry.Value, out bool foundError, out Exception exception);
this.currentSyntaxHasErrors = foundError;
this.currentSyntaxException = exception;
this.currentSyntaxSource = entry.Value;
}
if (this.currentSyntaxHasErrors)
{
FancyColor.PushBlend(start.Lerp(new FancyColor(0.68f, 0.2f, 0.2f), this.SelectionAnimFloat), FancyColor.BlendMode.Overlay);
}
else
{
FancyColor.PushBlend(start.Lerp(end, this.SelectionAnimFloat), FancyColor.BlendMode.Overlay);
}
}
else
{
FancyColor.PushBlend(start.Lerp(end, this.SelectionAnimFloat), FancyColor.BlendMode.Overlay);
}
}
isCellPressed = DrawCell(entryRect, isEven);
this.DrawEntry(entryRect, entry, GUIUtility.GetControlID(hint, FocusType.Keyboard), table, sharedEntry);
if (isSelected)
{
if (OdinLocalizationConfig.Instance.useSyntaxHighlighter && entry.IsSmart && this.currentSyntaxHasErrors)
{
Rect errorRect = entryRect.AlignLeft(OdinLocalizationConstants.ROW_MENU_WIDTH).AlignMiddle(16);
SdfIcons.DrawIcon(errorRect, SdfIconType.ExclamationOctagonFill,
Event.current.IsMouseOver(errorRect) ? new Color(1, 1, 1, 1f) : new Color(1, 1, 1, 0.6f));
if (Event.current.OnMouseDown(errorRect, 0))
{
this.RelatedWindow.ShowToast(ToastPosition.BottomLeft,
SdfIconType.ExclamationOctagonFill,
this.currentSyntaxErrorMessage,
new Color(0.68f, 0.2f, 0.2f),
20.0f);
if (this.currentSyntaxException != null)
{
Debug.LogException(this.currentSyntaxException);
}
}
}
FancyColor.PopBlend();
}
if (isCellPressed)
{
if (entry is null)
{
entry = table.Asset.AddEntry(sharedEntry.Id, string.Empty);
}
this.SelectEntry(entry);
}
break;
}
}
}
}
private void DrawEntry(Rect position, StringTableEntry entry, int id, OdinGUITable<StringTable> table, SharedTableData.SharedTableEntry sharedEntry)
{
bool changed;
string value;
Rect smartToggleRect = position.TakeFromRight(OdinLocalizationConstants.ROW_MENU_WIDTH);
position.TakeFromLeft(OdinLocalizationConstants.ROW_MENU_WIDTH);
if (entry?.Value is null)
{
value = OdinLocalizationGUI.TextField(position, string.Empty, out changed, id);
}
else if (OdinLocalizationConfig.Instance.useSyntaxHighlighter && entry.IsSmart && entry == this.CurrentSelectedEntry)
{
value = OdinLocalizationGUI.TextFieldSyntaxHighlighted(position, entry.Value, this.currentSyntaxHighlightedText, out changed, id);
if (changed)
{
this.currentSyntaxHighlightedText = OdinLocalizationSyntaxHighlighter.HighlightAsRichText(value);
this.currentSyntaxErrorMessage = OdinLocalizationSyntaxHighlighter.GetErrorMessage(value, out bool foundError, out Exception exception);
this.currentSyntaxHasErrors = foundError;
this.currentSyntaxException = exception;
this.currentSyntaxSource = value;
}
}
else
{
value = OdinLocalizationGUI.TextField(position, entry.Value, out changed, id);
}
if (changed)
{
if (entry == null)
{
entry = table.Asset.AddEntry(sharedEntry.Id, value);
}
Undo.RecordObject(entry.Table, "Modified String Table Entry Text");
entry.Value = value;
OdinLocalizationEvents.RaiseTableEntryModified(entry.SharedEntry);
EditorUtility.SetDirty(entry.Table);
}
smartToggleRect = smartToggleRect.AlignMiddle(16);
if (entry == null)
{
SdfIcons.DrawIcon(smartToggleRect, SdfIconType.Lightbulb, new Color(1, 1, 1, Event.current.IsMouseOver(smartToggleRect) ? 0.8f : 0.3f));
if (Event.current.OnMouseDown(smartToggleRect, 0))
{
Undo.RecordObject(table.Asset, "Added String Table Entry By Smart Toggle");
entry = table.Asset.AddEntry(sharedEntry.Id, string.Empty);
entry.IsSmart = !entry.IsSmart;
EditorUtility.SetDirty(table.Asset);
}
}
else
{
SdfIcons.DrawIcon(smartToggleRect,
entry.IsSmart ? SdfIconType.LightbulbFill : SdfIconType.Lightbulb,
new Color(1, 1, 1, Event.current.IsMouseOver(smartToggleRect) ? 0.8f : 0.3f));
if (Event.current.OnMouseDown(smartToggleRect, 0))
{
Undo.RecordObject(entry.Table, "Toggled Smart Flag On String Entry");
entry.IsSmart = !entry.IsSmart;
EditorUtility.SetDirty(entry.Table);
}
}
GUI.Label(smartToggleRect, GUIHelper.TempContent(string.Empty, "Toggle Smart String"));
}
protected override void MeasureAllEntries()
{
for (var i = 0; i < this.SharedEntries.Length; i++)
{
this.MeasureEntry(this.SharedEntries[i]);
}
this.HasGUIChanged = true;
}
protected override void MeasureVisibleEntries(ref OdinGUIScrollView.VisibleItems visibleItems)
{
int dataOffset = visibleItems.Offset;
for (var i = 0; i < visibleItems.Length; i++)
{
if (!visibleItems.HasAssociatedData(i))
{
continue;
}
var sharedEntry = visibleItems.GetAssociatedData<SharedTableData.SharedTableEntry>(i);
this.MeasureEntry(sharedEntry);
this.EntryScrollView.ReallocateRect(dataOffset + i, this.SharedEntryHeights[sharedEntry.Id], sharedEntry);
}
}
private void MeasureEntry(SharedTableData.SharedTableEntry sharedEntry)
{
float height = OdinLocalizationConstants.ROW_HEIGHT;
for (var i = 0; i < this.GUITables.Count; i++)
{
OdinGUITable<StringTable> currentTable = this.GUITables[i];
switch (currentTable.Type)
{
case OdinGUITable<StringTable>.GUITableType.Default:
StringTableEntry strEntry = currentTable.Asset.GetEntry(sharedEntry.Id);
if (strEntry is null)
{
continue;
}
#if USING_WIDTH_NON_PERCENT
float strEntryHeight = MeasureText(strEntry.Value, currentTable.Width);
#else
float strEntryHeight = MeasureText(strEntry.Value, currentTable.Width);
#endif
if (strEntryHeight > height)
{
height = strEntryHeight;
}
break;
case OdinGUITable<StringTable>.GUITableType.Key:
#if USING_WIDTH_NON_PERCENT
float keyHeight = MeasureText(sharedEntry.Key, currentTable.Width);
#else
float keyHeight = MeasureText(sharedEntry.Key, currentTable.Width);
#endif
if (keyHeight > height)
{
height = keyHeight;
}
break;
}
}
this.SharedEntryHeights[sharedEntry.Id] = height;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b8803d1557464e9a9d34713c9c9755af
timeCreated: 1704811189

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b8f34a530ab34e11b148c6c45d14fd12
timeCreated: 1704797131

View File

@@ -0,0 +1,23 @@
//-----------------------------------------------------------------------
// <copyright file="OdinTemplateMetadata.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Localization.Metadata;
namespace Sirenix.OdinInspector.Modules.Localization.Editor
{
#if false
[HideInInspector] // ReadOnly
[Metadata(AllowedTypes = MetadataType.SharedTableData, AllowMultiple = false)]
public class OdinTemplateMetadata : IMetadata
{
public Type TableAssetType;
public List<Type> MetadataExpected = new List<Type>();
}
#endif
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 50bcb1c3eb1c42b3b6a84c57b1c240dd
timeCreated: 1707729097

View File

@@ -0,0 +1,38 @@
//-----------------------------------------------------------------------
// <copyright file="Toggle.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using Sirenix.OdinInspector.Editor.Internal;
using UnityEngine;
namespace Sirenix.OdinInspector.Modules.Localization.Editor
{
public class Toggle
{
public string Label;
public float StartXMin;
public float StartXMax;
public Color StartColor;
public Rect CurrentThumbRect;
public Color CurrentColor;
public bool Enabled;
public SirenixAnimationUtility.InterpolatedFloat T1 = new SirenixAnimationUtility.InterpolatedFloat {Destination = 1f};
public SirenixAnimationUtility.InterpolatedFloat T2 = new SirenixAnimationUtility.InterpolatedFloat {Destination = 1f};
private bool _toggled;
public bool Toggled
{
get => this._toggled;
set
{
this._toggled = value;
this.StartXMin = this.CurrentThumbRect.xMin;
this.StartXMax = this.CurrentThumbRect.xMax;
this.StartColor = this.CurrentColor;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e4e8615bf94265a4a96bf4c23380de7a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7cf3a6e0f9c8495d843c313b4437232b
timeCreated: 1704797609

View File

@@ -0,0 +1,68 @@
//-----------------------------------------------------------------------
// <copyright file="OdinLocalizationAssetCache.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using Sirenix.Config;
using UnityEditor;
using UnityEngine.Localization.Tables;
namespace Sirenix.OdinInspector.Modules.Localization.Editor.Internal
{
public static class OdinLocalizationAssetCache
{
private readonly struct AssetIdentifier
{
public readonly Type AssetType;
public readonly string Guid;
public AssetIdentifier(Type assetType, string guid)
{
this.AssetType = assetType;
this.Guid = guid;
}
}
private static readonly Dictionary<AssetIdentifier, UnityEngine.Object> Assets = new Dictionary<AssetIdentifier, UnityEngine.Object>();
public static UnityEngine.Object Get(string guid, Type assetType)
{
if (string.IsNullOrEmpty(guid))
{
return null;
}
var identifier = new AssetIdentifier(assetType, guid);
if (Assets.TryGetValue(identifier, out UnityEngine.Object result))
{
return result;
}
string path = AssetDatabase.GUIDToAssetPath(guid);
result = AssetDatabase.LoadAssetAtPath(path, assetType);
Assets.Add(identifier, result);
return result;
}
public static UnityEngine.Object Get(SharedTableData.SharedTableEntry sharedEntry, AssetTable assetTable, Type assetType)
{
AssetTableEntry entry = assetTable.GetEntry(sharedEntry.Id);
if (entry == null || entry.IsEmpty)
{
return null;
}
return Get(entry.Guid, assetType);
}
public static void Clear() => Assets.Clear();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f5b97d1f7b884ac99cc8ebcdeb3363a7
timeCreated: 1704809501

View File

@@ -0,0 +1,68 @@
//-----------------------------------------------------------------------
// <copyright file="OdinLocalizationEditorSettings.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using UnityEditor;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.Localization;
using UnityEngine;
using UnityEngine.Localization.Settings;
namespace Sirenix.OdinInspector.Modules.Localization.Editor.Internal
{
public static class OdinLocalizationEditorSettings
{
public static LocalizationEditorSettings Instance =>
(LocalizationEditorSettings) OdinLocalizationReflectionValues.LocalizationEditorSettings_Instance.GetValue(null);
public static AddressableAssetSettings GetAddressableAssetSettings(bool create)
{
return OdinLocalizationReflectionValues.LocalizationEditorSettings_GetAddressableAssetSettingsFunc(create);
}
public static bool IsTableNameValid(Type collectionType, string name, out string errorMessage)
{
errorMessage = (string) OdinLocalizationReflectionValues.LocalizationEditorSettings_IsTableNameValid.Invoke(Instance,
new object[] {collectionType, name});
return string.IsNullOrEmpty(errorMessage);
}
public static bool IsTableNameValid(Type collectionType, string name)
{
return IsTableNameValid(collectionType, name, out string _);
}
public static bool CreateDefaultLocalizationSettingsAsset()
{
if (LocalizationEditorSettings.ActiveLocalizationSettings != null)
{
return false;
}
string localizationSettingsPath = EditorUtility.SaveFilePanelInProject("Create Localization Settings",
"Localization Settings",
"asset",
"Create the Localization Settings asset");
if (string.IsNullOrEmpty(localizationSettingsPath))
{
return false;
}
var settings = ScriptableObject.CreateInstance<LocalizationSettings>();
settings.name = "Default Localization Settings";
AssetDatabase.CreateAsset(settings, localizationSettingsPath);
AssetDatabase.SaveAssets();
LocalizationEditorSettings.ActiveLocalizationSettings = settings;
return true;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 645f3592e85448d7a963218911547f3c
timeCreated: 1706732179

View File

@@ -0,0 +1,50 @@
//-----------------------------------------------------------------------
// <copyright file="OdinLocalizationEvents.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using UnityEditor.Localization;
using UnityEngine.Localization.Tables;
namespace Sirenix.OdinInspector.Modules.Localization.Editor.Internal
{
public static class OdinLocalizationEvents
{
private static readonly Action<SharedTableData.SharedTableEntry> RaiseTableEntryModifiedAction;
private static readonly Action<LocalizationTableCollection, SharedTableData.SharedTableEntry> RaiseTableEntryAddedAction;
private static readonly Action<LocalizationTableCollection, SharedTableData.SharedTableEntry> RaiseTableEntryRemovedAction;
private static readonly Action<AssetTableCollection, AssetTable, AssetTableEntry> RaiseAssetTableEntryAddedAction;
private static readonly Action<AssetTableCollection, AssetTable, AssetTableEntry, string> RaiseAssetTableEntryRemovedAction;
static OdinLocalizationEvents()
{
RaiseTableEntryModifiedAction = OdinLocalizationReflectionValues.Create_RaiseTableEntryModified_Method_Delegate(LocalizationEditorSettings.EditorEvents);
RaiseTableEntryAddedAction = OdinLocalizationReflectionValues.Create_RaiseTableEntryAdded_Method_Delegate(LocalizationEditorSettings.EditorEvents);
RaiseTableEntryRemovedAction = OdinLocalizationReflectionValues.Create_RaiseTableEntryRemoved_Method_Delegate(LocalizationEditorSettings.EditorEvents);
RaiseAssetTableEntryAddedAction =
OdinLocalizationReflectionValues.Create_AssetTableEntryAdded_Method_Delegate(LocalizationEditorSettings.EditorEvents);
RaiseAssetTableEntryRemovedAction =
OdinLocalizationReflectionValues.Create_AssetTableEntryRemoved_Method_Delegate(LocalizationEditorSettings.EditorEvents);
}
public static void RaiseTableEntryModified(SharedTableData.SharedTableEntry sharedEntry) => RaiseTableEntryModifiedAction(sharedEntry);
public static void RaiseTableEntryAdded(LocalizationTableCollection collection, SharedTableData.SharedTableEntry sharedEntry) =>
RaiseTableEntryAddedAction(collection, sharedEntry);
public static void RaiseTableEntryRemoved(LocalizationTableCollection collection, SharedTableData.SharedTableEntry sharedEntry) =>
RaiseTableEntryRemovedAction(collection, sharedEntry);
public static void RaiseAssetTableEntryAdded(AssetTableCollection assetCollection, AssetTable table, AssetTableEntry entry) =>
RaiseAssetTableEntryAddedAction(assetCollection, table, entry);
public static void RaiseAssetTableEntryRemoved(AssetTableCollection assetCollection, AssetTable table, AssetTableEntry entry, string guid) =>
RaiseAssetTableEntryRemovedAction(assetCollection, table, entry, guid);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a7cf7883289c4de29b6e2ae8a072c3bd
timeCreated: 1705050052

View File

@@ -0,0 +1,438 @@
//-----------------------------------------------------------------------
// <copyright file="OdinLocalizationGUI.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using Sirenix.OdinInspector.Editor;
using Sirenix.OdinInspector.Editor.Internal;
using Sirenix.Reflection.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using UnityEditor;
using UnityEngine;
namespace Sirenix.OdinInspector.Modules.Localization.Editor.Internal
{
// TODO: update name
public static class OdinLocalizationGUI
{
public static GUIStyle RichLabelTextCenteredMultiLine
{
get
{
if (_richLabelTextCenteredMultiLine == null)
{
_richLabelTextCenteredMultiLine = new GUIStyle(SirenixGUIStyles.MultiLineCenteredLabel)
{
richText = true
};
}
return _richLabelTextCenteredMultiLine;
}
}
public static GUIStyle CardTitleWhite
{
get
{
if (_cardTitleWhite == null)
{
_cardTitleWhite = new GUIStyle(SirenixGUIStyles.MultiLineWhiteLabel)
{
fontSize = 12,
fontStyle = FontStyle.Bold
};
}
return _cardTitleWhite;
}
}
public static FancyColor Background => EditorGUIUtility.isProSkin ? Color.black : Color.white;
// TODO: LIGHT MODE
public static FancyColor MenuBackground => EditorGUIUtility.isProSkin ? DarkMenuBackground : LightMenuBackground;
public static FancyColor WindowBackground => EditorGUIUtility.isProSkin ? DarkWindowBackground : LightWindowBackground;
public static FancyColor Panel => EditorGUIUtility.isProSkin ? DarkPanel : LightPanel;
public static FancyColor PanelGap => EditorGUIUtility.isProSkin ? DarkPanelGap : LightPanelGap;
public static FancyColor Selected => EditorGUIUtility.isProSkin ? DarkSelected : LightSelected;
public static FancyColor TabsBackground => EditorGUIUtility.isProSkin ? DarkTabsBackground : LightTabsBackground;
public static FancyColor ColumnBackground => EditorGUIUtility.isProSkin ? DarkColumnBackground : LightColumnBackground;
public static FancyColor RowEvenBackground => EditorGUIUtility.isProSkin ? DarkRowEvenBackground : LightRowEvenBackground;
public static FancyColor RowEvenBackground2 => EditorGUIUtility.isProSkin ? DarkRowEvenBackground2 : LightRowEvenBackground2;
public static FancyColor RowOddBackground => EditorGUIUtility.isProSkin ? DarkRowOddBackground : LightRowOddBackground;
public static FancyColor RowOddBackground2 => EditorGUIUtility.isProSkin ? DarkRowOddBackground2 : LightRowOddBackground2;
public static FancyColor RowBorder => EditorGUIUtility.isProSkin ? DarkRowBorder : LightRowBorder;
public static FancyColor RowBorderHover => EditorGUIUtility.isProSkin ? DarkRowBorderHover : LightRowBorderHover;
public static FancyColor Tab => EditorGUIUtility.isProSkin ? DarkTab : LightTab;
public static FancyColor TabHover => EditorGUIUtility.isProSkin ? DarkTabHover : LightTabHover;
public static FancyColor TabSelected => EditorGUIUtility.isProSkin ? DarkTabSelected : LightTabSelected;
public static FancyColor Checkerboard => DarkCheckerboard;
public static FancyColor CheckerboardBorder => DarkCheckerboardBorder;
public static FancyColor DarkMenuBackground;
public static FancyColor DarkWindowBackground;
public static FancyColor DarkPanel;
public static FancyColor DarkPanelGap;
public static FancyColor DarkSelected;
public static FancyColor DarkTabsBackground;
public static FancyColor DarkColumnBackground;
public static FancyColor DarkRowEvenBackground;
public static FancyColor DarkRowEvenBackground2;
public static FancyColor DarkRowOddBackground;
public static FancyColor DarkRowOddBackground2;
public static FancyColor DarkRowBorder;
public static FancyColor DarkRowBorderHover;
public static FancyColor DarkTab;
public static FancyColor DarkTabHover;
public static FancyColor DarkTabSelected;
public static FancyColor LightMenuBackground;
public static FancyColor LightWindowBackground;
public static FancyColor LightPanel;
public static FancyColor LightPanelGap;
public static FancyColor LightSelected;
public static FancyColor LightTabsBackground;
public static FancyColor LightColumnBackground;
public static FancyColor LightRowEvenBackground;
public static FancyColor LightRowEvenBackground2;
public static FancyColor LightRowOddBackground;
public static FancyColor LightRowOddBackground2;
public static FancyColor LightRowBorder;
public static FancyColor LightRowBorderHover;
public static FancyColor LightTab;
public static FancyColor LightTabHover;
public static FancyColor LightTabSelected;
public static FancyColor DarkCheckerboard;
public static FancyColor DarkCheckerboardBorder;
private static GUIStyle DefaultTextStyle;
private static GUIStyle _richLabelTextCenteredMultiLine;
private static GUIStyle _cardTitleWhite;
static OdinLocalizationGUI()
{
DarkWindowBackground = new Color(0.15f, 0.15f, 0.15f, 1.0f);
LightWindowBackground = new Color(0.358f, 0.358f, 0.358f, 1.0f);
DarkPanel = new Color(0.2156863f, 0.2156863f, 0.2156863f, 1.0f);
DarkPanelGap = new Color(0.0856863f, 0.0856863f, 0.0856863f, 1.0f);
LightPanel = new Color(0.761f, 0.761f, 0.761f, 1.0f);
LightPanelGap = new Color(0.532f, 0.532f, 0.532f, 1.0f);
DarkSelected = new Color(0.7076807f, 0.8213042f, 0.8773585f, 1.0f);
LightSelected = new Color(0.358f, 0.446f, 0.509f, 1.0f);
DarkTabsBackground = new Color(0.1886792f, 0.1886792f, 0.1886792f, 1.0f);
LightTabsBackground = new Color(0.538f, 0.538f, 0.538f, 1.0f);
DarkColumnBackground = new Color(0.099f, 0.099f, 0.099f, 1.0f);
LightColumnBackground = new FancyColor(0.84f);
var rowBaseBlend = new FancyColor(0.42f);
#region DARK_ROW
var darkRowEvenBackground = new FancyColor(0.2f);
var darkRowOddBackground = new FancyColor(0.16f);
DarkRowEvenBackground = darkRowEvenBackground;
DarkRowOddBackground = darkRowOddBackground;
DarkRowEvenBackground2 = darkRowEvenBackground.Blend(rowBaseBlend, FancyColor.BlendMode.Overlay);
DarkRowOddBackground2 = darkRowOddBackground.Blend(rowBaseBlend, FancyColor.BlendMode.Overlay);
DarkRowBorder = new Color(0, 0, 0, 0.3607843f);
DarkRowBorderHover = new Color(1, 1, 1, 0.2470588f);
#endregion
#region LIGHT_ROW
var lightRowEvenBackground = new FancyColor(0.711f);
var lightRowOddBackground = new FancyColor(0.611f);
LightRowEvenBackground = lightRowEvenBackground;
LightRowOddBackground = lightRowOddBackground;
LightRowEvenBackground2 = lightRowEvenBackground.Blend(rowBaseBlend, FancyColor.BlendMode.Overlay);
LightRowOddBackground2 = lightRowOddBackground.Blend(rowBaseBlend, FancyColor.BlendMode.Overlay);
LightRowBorder = new Color(0, 0, 0, 0.2f);
LightRowBorderHover = new Color(1, 1, 1, 0.4f);
#endregion
DarkTab = new Color(0.254717f, 0.254717f, 0.254717f, 1.0f);
DarkTabHover = new Color(0.277f, 0.277f, 0.277f, 1.0f);
DarkTabSelected = new Color(0.312f, 0.312f, 0.312f, 1.0f);
float scalar = 0.925f;
LightTab = new FancyColor(0.85f * scalar);
LightTabHover = new FancyColor(0.915f * scalar);
LightTabSelected = new FancyColor(0.95f * scalar);
DarkCheckerboard = new Color(0.298f, 0.298f, 0.298f, 1.0f);
DarkCheckerboardBorder = new Color(0, 0, 0, 0.5529412f);
DefaultTextStyle = new GUIStyle(SirenixGUIStyles.MultiLineCenteredLabel);
DefaultTextStyle.focused.textColor = DefaultTextStyle.normal.textColor;
DarkMenuBackground = DarkPanel;
LightMenuBackground = new FancyColor(0.84f);
}
public static string TextField(Rect position, string text, out bool changed, int id)
{
if (id == 0)
{
GUI.Label(position, text, SirenixGUIStyles.MultiLineCenteredLabel);
changed = false;
return text;
}
Color lastCursorColor = GUI.skin.settings.cursorColor;
bool isMouseDown = Event.current.type == EventType.MouseDown;
if (isMouseDown)
{
GUI.skin.settings.cursorColor = Color.clear;
}
text = EditorGUI_Internals.DoTextField(id, position, text, DefaultTextStyle, null, out changed, false, true, false);
if (isMouseDown)
{
GUI.skin.settings.cursorColor = lastCursorColor;
}
return text;
}
public static string TextFieldSyntaxHighlighted(Rect position, string text, string syntaxRichText, out bool changed, int id)
{
if (string.IsNullOrEmpty(syntaxRichText))
{
return TextField(position, text, out changed, id);
}
GUIHelper.PushContentColor(Color.clear);
text = EditorGUI_Internals.DoTextField(id, position, text, SirenixGUIStyles.MultiLineCenteredLabel, null, out changed, false, true, false);
GUIHelper.PopContentColor();
GUI.Label(position, syntaxRichText, RichLabelTextCenteredMultiLine);
return text;
}
public static bool ObjectPickerButton(Rect position)
{
bool isMouseOver = Event.current.IsMouseOver(position);
var c1 = EditorGUIUtility.isProSkin
? new Color(1, 1, 1, isMouseOver ? 0.8f : 0.3f)
: new Color(0, 0, 0, isMouseOver ? 0.8f : 0.3f);
SdfIcons.DrawIcon(position, SdfIconType.StopCircle, c1);
return Event.current.OnMouseDown(position, 0);
}
public static TEnum Tabs<TEnum>(Rect rect, TEnum value, float width) where TEnum : Enum
{
EnumTypeUtilities<TEnum>.EnumMember[] infos = EnumTypeUtilities<TEnum>.VisibleEnumMemberInfos;
for (var i = 0; i < infos.Length; i++)
{
Rect itemRect = rect.TakeFromLeft(width);
bool isMouseOver = Event.current.IsMouseOver(itemRect);
bool isSelected = value.Equals(infos[i].Value);
bool isPressed = Event.current.OnMouseDown(itemRect, 0);
Color tabColor;
if (isSelected)
{
tabColor = TabSelected;
}
else if (isMouseOver)
{
tabColor = TabHover;
}
else
{
tabColor = Tab;
}
SirenixEditorGUI.DrawSolidRect(itemRect, tabColor);
Vector2 textSize = EditorStyles.label.CalcSize(GUIHelper.TempContent(infos[i].NiceName));
float iconSize = textSize.y;
float size = iconSize + 2.0f + textSize.x + 1.0f;
if (size > itemRect.width)
{
if (iconSize > itemRect.width)
{
continue;
}
SdfIcons.DrawIcon(itemRect.AlignCenter(iconSize), infos[i].Icon);
}
else
{
Rect contentRect = itemRect.AlignCenter(size);
Rect iconRect = contentRect.TakeFromLeft(iconSize).AlignMiddle(iconSize);
SdfIcons.DrawIcon(iconRect, infos[i].Icon);
contentRect.TakeFromLeft(2);
GUI.Label(contentRect, infos[i].NiceName);
}
if (isPressed)
{
value = infos[i].Value;
}
}
return value;
}
public static bool OverlaidButton(Rect position, string text, SdfIconType icon = SdfIconType.None, GUIStyle labelStyle = null, bool invert = false)
{
const float ICON_SIZE = 18;
const float SPACE = 4;
bool isMouseOver = Event.current.IsMouseOver(position);
ref SirenixAnimationUtility.InterpolatedFloat t = ref SirenixAnimationUtility.GetTemporaryFloat(position, isMouseOver ? 1.0f : 0.0f);
t.ChangeDestination(isMouseOver ? 1.0f : 0.0f);
t.Move(1.0f / 0.15f, Easing.OutQuad);
float value = t.GetValue();
float inverseValue = 1.0f - value;
if (invert)
{
SirenixEditorGUI.DrawRoundRect(position, new Color(0, 0, 0, 0.2f * inverseValue), 5.0f, new Color(0, 0, 0, 0.05f * inverseValue), 1);
SirenixEditorGUI.DrawRoundRect(position, new Color(0, 0, 0, 0.4f * value), 5.0f, new Color(1.0f, 1.0f, 1.0f, 0.05f * value), 1);
}
else
{
SirenixEditorGUI.DrawRoundRect(position, new Color(1, 1, 1, 0.2f * inverseValue), 5.0f, new Color(0, 0, 0, 0.05f * inverseValue), 1);
SirenixEditorGUI.DrawRoundRect(position, new Color(1, 1, 1, 0.4f * value), 5.0f, new Color(1.0f, 1.0f, 1.0f, 0.05f * value), 1);
}
labelStyle = labelStyle ?? SirenixGUIStyles.WhiteLabelCentered;
if (icon == SdfIconType.None)
{
GUI.Label(position, text, labelStyle);
}
else
{
float textWidth = SirenixGUIStyles.WhiteLabelCentered.CalcWidth(text);
Rect contentPosition = position.AlignCenter(ICON_SIZE + SPACE + textWidth);
SdfIcons.DrawIcon(contentPosition.AlignLeft(ICON_SIZE), icon, Color.white);
GUI.Label(contentPosition.AlignRight(textWidth), text, labelStyle);
}
return Event.current.OnMouseDown(position, 0);
}
public static bool Metadata(Type metadataType, bool isFirst)
{
var isRemoved = false;
if (isFirst)
{
EditorGUI.DrawRect(GUILayoutUtility.GetRect(0, 1), new FancyColor(0.06603771f));
}
Vector2 padding = new Vector2(4, 4);
Rect rect = GUILayoutUtility.GetRect(0, 24);
if (Event.current.IsMouseOver(rect))
{
EditorGUI.DrawRect(rect, new FancyColor(0.2784314f));
}
else
{
EditorGUI.DrawRect(rect, new FancyColor(0.2431373f));
}
EditorGUI.DrawRect(GUILayoutUtility.GetRect(0, 1), new FancyColor(0.06603771f));
var iconWidth = 16.0f;
var iconPadding = new Vector2(2, 2);
SdfIcons.DrawIcon(rect.TakeFromLeft(iconWidth).Padding(iconPadding.x, iconPadding.y), SdfIconType.GripVertical);
rect = rect.Padding(padding.x, 0);
SdfIcons.DrawIcon(rect.TakeFromLeft(iconWidth).Padding(iconPadding.x, iconPadding.y), SdfIconType.Braces);
rect.TakeFromLeft(5);
GUI.Label(rect, metadataType.GetNiceName(), SirenixGUIStyles.BoldLabel);
var removeRect = rect.TakeFromRight(iconWidth).Padding(iconPadding.x, iconPadding.y);
SdfIcons.DrawIcon(removeRect, SdfIconType.X);
if (GUI.Button(removeRect, GUIContent.none, GUIStyle.none))
{
isRemoved = true;
}
return isRemoved;
}
public static void DrawRoundBlur20(Rect position, Color color)
{
position = position.Expand(63);
SirenixEditorGUI.DrawTextureSliced(position, OdinLocalizationGUITextures.RoundBlur20, color, 63);
}
public static void DrawRoundBlur6(Rect position, Color color)
{
position = position.Expand(10);
SirenixEditorGUI.DrawTextureSliced(position, OdinLocalizationGUITextures.RoundBlur6, color, 10);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 12ea4fb9c8db4471801a31e2e0b353de
timeCreated: 1704803170

View File

@@ -0,0 +1,310 @@
//-----------------------------------------------------------------------
// <copyright file="OdinLocalizationGUITextures.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using Sirenix.Utilities.Editor;
using UnityEngine;
namespace Sirenix.OdinInspector.Modules.Localization.Editor.Internal
{
public static class OdinLocalizationGUITextures
{
private const float FADE_STRENGTH = 0.4f;
public static Texture2D LeftToRightFade
{
get
{
if (leftToRightFade != null)
{
return leftToRightFade;
}
leftToRightFade = new Texture2D(32, 32)
{
hideFlags = HideFlags.HideAndDontSave
};
var pixels = new Color[32 * 32];
var index = 0;
for (var x = 0; x < 32; x++)
{
for (var y = 0; y < 32; y++)
{
pixels[index++] = Color.Lerp(Color.white, Color.clear, Mathf.Pow(y / 31.0f, FADE_STRENGTH));
}
}
leftToRightFade.SetPixels(pixels);
leftToRightFade.Apply();
CleanupUtility.DestroyObjectOnAssemblyReload(leftToRightFade);
return leftToRightFade;
}
}
public static Texture2D TopToBottomFade
{
get
{
if (topToBottomFade != null)
{
return topToBottomFade;
}
topToBottomFade = new Texture2D(32, 32)
{
hideFlags = HideFlags.HideAndDontSave
};
var pixels = new Color[32 * 32];
var index = 0;
for (var x = 0; x < 32; x++)
{
for (var y = 0; y < 32; y++)
{
pixels[index++] = Color.Lerp(Color.white, Color.clear, Mathf.Pow(1.0f - x / 31.0f, FADE_STRENGTH));
}
}
topToBottomFade.SetPixels(pixels);
topToBottomFade.Apply();
CleanupUtility.DestroyObjectOnAssemblyReload(topToBottomFade);
return topToBottomFade;
}
}
public static Texture2D BottomToTopFade
{
get
{
if (bottomToTopFade != null)
{
return bottomToTopFade;
}
bottomToTopFade = new Texture2D(32, 32)
{
hideFlags = HideFlags.HideAndDontSave
};
var pixels = new Color[32 * 32];
var index = 0;
for (var x = 0; x < 32; x++)
{
for (var y = 0; y < 32; y++)
{
pixels[index++] = Color.Lerp(Color.white, Color.clear, Mathf.Pow(x / 31.0f, FADE_STRENGTH));
}
}
bottomToTopFade.SetPixels(pixels);
bottomToTopFade.Apply();
CleanupUtility.DestroyObjectOnAssemblyReload(bottomToTopFade);
return bottomToTopFade;
}
}
public static Texture2D RoundBlur6
{
get
{
if (roundBlur6 == null)
{
const string BASE64 =
"iVBORw0KGgoAAAANSUhEUgAAAGYAAABmCAYAAAA53+RiAAAFDElEQVR4Ae2dzZIURRSFaQFBDGBBsHGB76Ju0BfxkXwRZaXvIgs3BhEiIT/DYHu+pG+R3XN6mDa6uZuTEacr61ZWVcb3kVXFaq5dSwuBELg6gVUN" +
"Xa/X1a1tHWO7G8bU8RqfrSdQYNnuhjPq+Dh7tXqP9cbY2/4p4J+pTK5PqRrbGqdu2iUEAP/vlHfqV6rO6VuCdsUAm5SQm+p/vgl9wjklSN3RIqlIvN/OkAv+uQ693eRMW8I+khhDW87bFcNBoFO/pdxW7ihfbML+" +
"LCdCBOSSVqulpLzW2FebvNSW/TcKxxG0tFnMvFKQgpC7yn3l3qZPreTkcSYYH2klhpWBBGS8UJ4r/AOHIY1xNZbtWBlsqzGQE4CPlAfKQ+Vb5XvlkcKjLe1wAjy6nio/K78pcKbxGKvHWT3SPrzA9VWGFKCzKlgl" +
"SPlK+VF5rKQdj8ATXeon5Q/lmcIKYjWd6atsyKmlpNqQxBcYcninIOcbJVIE4cgNpjyFYAxrmMN+eWc7MSwxBvMo+0FJOw0BXg0whjXMLxWDqHrH8Eh7pKSdhgBsYWw/pnZXDPt8qZUclljaaQjAtqTAHPb2Ucbt" +
"OTjLoZZ2OgIsgJIC96XNO9iq/8uUoGVgOichUJzZFv9xo1nMfOetQfOB9I9KYC/nfWKOevdc7HACTgwW0z4tgQvMnZhPO6XczRKIGIulvxgx/Q7sDCLGYukvRky/AzuDiLFY+osR0+/AziBiLJb+YsT0O7AziBiL" +
"pb8YMf0O7AwixmLpL0ZMvwM7g4ixWPqLEdPvwM4gYiyW/mLE9DuwM4gYi6W/GDH9DuwMIsZi6S9GTL8DO4OIsVj6ixHT78DOIGIslv5ixPQ7sDOIGIulvxgx/Q7sDCLGYukvRky/AzuDiLFY+osR0+/AziBiLJb+" +
"YsT0O7AziBiLpb8YMf0O7AwixmLpL0ZMvwM7g4ixWPqLEdPvwM4gYiyW/mLE9DuwM4gYi6W/GDH9DuwMIsZi6S9GTL8DO4OIsVj6ixHT78DOIGIslv5ixPQ7sDOIGIulvxgx/Q7sDCLGYukvRky/AzuDiLFY+osR" +
"0+/AziBiLJb+YsT0O7AziBiLpb8YMf0O7AwixmLpL0ZMvwM7g4ixWPqLEdPvwM4gYiyW/mLE9DuwM4gYi6W/GDH9DuwMnJjxZ8vt6BRPReACcyfmVDfPdQ8gsE8MBi9YPOC6GXo1Ans5z2JqEH9PvnK1y2fU/yVQ" +
"nNkW/3GtWQyFGniu/tsxIj+nJABjWBf35V6zGIwxoKS8Vv9sGZnOsQnAFsazHByM5sQwkBNeKk/HqPycggBsYVxy6nE27rUr5p2qiHmlvFB+UdJOQwC2MIY1zGG/d8VwkCXG4L+VX5UnStpxCcAUtjCGNcy3xCx/" +
"fHm9XtO/odxSvlTuKw+Uh8p3ymPla+WmknY4AVbF7worBSl/Ks+U58o/yhvlfLVajVWzK4ZHG3JuK3eUuwqC7m361DiGHMYu56ufdpEAkHl3zO9tHl+sFITQr/fM+DorMUioVhdhSWGPVhfF6F9KSeG8iBGEj7Ri" +
"On/p8ugiJQTWMN96+c9idGw0BnAhLsoJ9THAKiElZf5wyMoRmKnBrho8iyksCe8UQr+kqPuhLUD1jqlq1QBPrk+pGtsap27aJQRq1ZQgRFSqxulDgB5l41IL3EnMOKCfOsZ2N4yp4/TT9hOof/Fsd8NZdXxcocSM" +
"nfyEQAhckcB/Z2rwteNGzXsAAAAASUVORK5CYII=";
byte[] bytes = Convert.FromBase64String(BASE64);
roundBlur6 = TextureUtilities.LoadImage(102, 102, bytes);
roundBlur6.hideFlags = HideFlags.HideAndDontSave;
CleanupUtility.DestroyObjectOnAssemblyReload(roundBlur6);
}
return roundBlur6;
}
}
public static Texture2D RoundBlur20
{
get
{
if (roundBlur20 == null)
{
const string BASE64 =
"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";
byte[] bytes = Convert.FromBase64String(BASE64);
roundBlur20 = TextureUtilities.LoadImage(193, 193, bytes);
roundBlur20.hideFlags = HideFlags.HideAndDontSave;
CleanupUtility.DestroyObjectOnAssemblyReload(roundBlur20);
}
return roundBlur20;
}
}
private static Texture2D leftToRightFade;
private static Texture2D topToBottomFade;
private static Texture2D bottomToTopFade;
private static Texture2D roundBlur6;
private static Texture2D roundBlur20;
public static void Clear()
{
if (leftToRightFade != null)
{
UnityEngine.Object.Destroy(leftToRightFade);
}
if (topToBottomFade != null)
{
UnityEngine.Object.Destroy(topToBottomFade);
}
if (bottomToTopFade != null)
{
UnityEngine.Object.Destroy(bottomToTopFade);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 78842ff15d254d9b8994383880b759f1
timeCreated: 1704903394

View File

@@ -0,0 +1,55 @@
//-----------------------------------------------------------------------
// <copyright file="OdinLocalizationMetadataRegistry.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine.Localization.Metadata;
namespace Sirenix.OdinInspector.Modules.Localization.Editor.Internal
{
public class OdinLocalizationMetadataRegistry
{
public static readonly List<Type> AssetEntryMetadataTypes = new List<Type>();
public static readonly List<Type> StringEntryMetadataTypes = new List<Type>();
public static readonly Dictionary<Type, bool> MetadataAllowsMultiple = new Dictionary<Type, bool>();
static OdinLocalizationMetadataRegistry()
{
TypeCache.TypeCollection metadataTypes = TypeCache.GetTypesDerivedFrom(typeof(IMetadata));
for (var i = 0; i < metadataTypes.Count; i++)
{
Type currentType = metadataTypes[i];
var attr = currentType.GetCustomAttribute<MetadataAttribute>();
if (attr is null)
{
MetadataAllowsMultiple[currentType] = true;
continue;
}
MetadataAllowsMultiple[currentType] = attr.AllowMultiple;
MetadataType currentAllowedTypes = attr.AllowedTypes;
if (currentAllowedTypes.HasFlag(MetadataType.StringTableEntry))
{
StringEntryMetadataTypes.Add(currentType);
}
if (currentAllowedTypes.HasFlag(MetadataType.AssetTableEntry))
{
AssetEntryMetadataTypes.Add(currentType);
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7deb6e0b5f6c4579ba2ead2af5620d3e
timeCreated: 1706509329

View File

@@ -0,0 +1,43 @@
//-----------------------------------------------------------------------
// <copyright file="OdinLocalizationReflectionValidator.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
#if SIRENIX_INTERNAL
using System.Collections;
using System.Reflection;
using Sirenix.OdinInspector.Editor.Validation;
using Sirenix.OdinInspector.Modules.Localization.Editor.Internal;
[assembly: RegisterValidator(typeof(OdinLocalizationReflectionValidator))]
namespace Sirenix.OdinInspector.Modules.Localization.Editor.Internal
{
public class OdinLocalizationReflectionValidator : GlobalValidator
{
public override IEnumerable RunValidation(ValidationResult result)
{
OdinLocalizationReflectionValues.EnsureInit();
FieldInfo[] fields = typeof(OdinLocalizationReflectionValues).GetFields(BindingFlags.Static | BindingFlags.Public);
for (var i = 0; i < fields.Length; i++)
{
if (fields[i].IsLiteral)
{
continue;
}
if (fields[i].GetValue(null) != null)
{
continue;
}
result.AddError($"[Odin Localization Module]: {fields[i].Name} was not found.");
}
return null;
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c12d37dc91fe4285bd661d45717bf34a
timeCreated: 1704797628

View File

@@ -0,0 +1,174 @@
//-----------------------------------------------------------------------
// <copyright file="OdinLocalizationReflectionValues.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Reflection;
using Sirenix.Serialization;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.Localization;
using UnityEngine.Localization;
using UnityEngine.Localization.Metadata;
using UnityEngine.Localization.Tables;
namespace Sirenix.OdinInspector.Modules.Localization.Editor.Internal
{
public static class OdinLocalizationReflectionValues
{
public const string TABLE_ENTRY_DATA__METADATA__PATH = "m_Metadata";
public const string METADATA_COLLECTION__ITEMS__PATH = "m_Items";
public const string TABLE_ENTRY__DATA__PATH = "Data";
public static readonly bool HasAPIForCustomUndo = false;
public static readonly PropertyInfo TableEntry_Data_Property;
public static readonly PropertyInfo AssetTableEntry_Data_Property;
public static readonly PropertyInfo StringTableEntry_Data_Property;
public static readonly FieldInfo TableEntryData_m_Metadata_Field;
public static readonly FieldInfo MetadataCollection_m_Items_Field;
public static readonly PropertyInfo LocalizationEditorSettings_Instance;
public static readonly MethodInfo LocalizationEditorSettings_GetAddressableAssetSettings;
public static readonly MethodInfo LocalizationEditorSettings_IsTableNameValid;
public static readonly MethodInfo RaiseTableEntryModified_Method;
public static readonly MethodInfo RaiseTableEntryAdded_Method;
public static readonly MethodInfo RaiseTableEntryRemoved_Method;
public static readonly MethodInfo RaiseAssetTableEntryAdded_Method;
public static readonly MethodInfo RaiseAssetTableEntryRemoved_Method;
public static readonly MethodInfo AssetTableCollection_SetEntryAssetType_PrivateMethod;
public static readonly MethodInfo AssetTableCollection_RemoveEntryAssetType_PrivateMethod;
public static readonly Type AddressHelper;
public static readonly MethodInfo FormatAssetLabelMethod;
public static readonly MethodInfo UpdateAssetGroupMethod;
private const BindingFlags INSTANCE_ALL = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private const BindingFlags INSTANCE_NON_PUBLIC = BindingFlags.Instance | BindingFlags.NonPublic;
private const BindingFlags STATIC_NON_PUBLIC = BindingFlags.Static | BindingFlags.NonPublic;
internal static Func<bool, AddressableAssetSettings> LocalizationEditorSettings_GetAddressableAssetSettingsFunc;
static OdinLocalizationReflectionValues()
{
TableEntry_Data_Property = typeof(TableEntry).GetProperty(TABLE_ENTRY__DATA__PATH, INSTANCE_ALL);
AssetTableEntry_Data_Property = typeof(AssetTableEntry).BaseType.GetProperty(TABLE_ENTRY__DATA__PATH, INSTANCE_ALL);
StringTableEntry_Data_Property = typeof(StringTableEntry).BaseType.GetProperty(TABLE_ENTRY__DATA__PATH, INSTANCE_ALL);
Type tableEntryDataType = TwoWaySerializationBinder.Default.BindToType("UnityEngine.Localization.Tables.TableEntryData, Unity.Localization");
TableEntryData_m_Metadata_Field = tableEntryDataType?.GetField(TABLE_ENTRY_DATA__METADATA__PATH, INSTANCE_ALL);
MetadataCollection_m_Items_Field = typeof(MetadataCollection).GetField(METADATA_COLLECTION__ITEMS__PATH, INSTANCE_ALL);
LocalizationEditorSettings_Instance = typeof(LocalizationEditorSettings).GetProperty("Instance", STATIC_NON_PUBLIC);
LocalizationEditorSettings_GetAddressableAssetSettings = typeof(LocalizationEditorSettings).GetMethod("GetAddressableAssetSettings",
INSTANCE_NON_PUBLIC);
LocalizationEditorSettings_IsTableNameValid = typeof(LocalizationEditorSettings).GetMethod("IsTableNameValid", INSTANCE_NON_PUBLIC);
RaiseTableEntryModified_Method = LocalizationEditorSettings.EditorEvents.GetType().GetMethod("RaiseTableEntryModified", INSTANCE_ALL);
RaiseTableEntryAdded_Method = LocalizationEditorSettings.EditorEvents.GetType().GetMethod("RaiseTableEntryAdded", INSTANCE_ALL);
RaiseTableEntryRemoved_Method = LocalizationEditorSettings.EditorEvents.GetType().GetMethod("RaiseTableEntryRemoved", INSTANCE_ALL);
RaiseAssetTableEntryAdded_Method = LocalizationEditorSettings.EditorEvents.GetType().GetMethod("RaiseAssetTableEntryAdded", INSTANCE_ALL);
RaiseAssetTableEntryRemoved_Method = LocalizationEditorSettings.EditorEvents.GetType().GetMethod("RaiseAssetTableEntryRemoved", INSTANCE_ALL);
AssetTableCollection_SetEntryAssetType_PrivateMethod = typeof(AssetTableCollection).GetMethod("SetEntryAssetType", INSTANCE_NON_PUBLIC);
AssetTableCollection_RemoveEntryAssetType_PrivateMethod = typeof(AssetTableCollection).GetMethod("RemoveEntryAssetType", INSTANCE_NON_PUBLIC);
if (LocalizationEditorSettings_Instance != null && LocalizationEditorSettings_GetAddressableAssetSettings != null)
{
LocalizationEditorSettings_GetAddressableAssetSettingsFunc =
(Func<bool, AddressableAssetSettings>) Delegate.CreateDelegate(typeof(Func<bool, AddressableAssetSettings>),
LocalizationEditorSettings_Instance.GetValue(null),
LocalizationEditorSettings_GetAddressableAssetSettings);
}
AddressHelper = TwoWaySerializationBinder.Default.BindToType("UnityEngine.Localization.AddressHelper");
FormatAssetLabelMethod = AddressHelper?.GetMethod("FormatAssetLabel",
BindingFlags.Static | BindingFlags.Public,
null,
new[] {typeof(LocaleIdentifier)},
null);
UpdateAssetGroupMethod = typeof(AssetTableCollection).GetMethod("UpdateAssetGroup",
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new[]
{
typeof(AddressableAssetSettings),
typeof(AddressableAssetEntry),
typeof(bool)
},
null);
HasAPIForCustomUndo = AddressHelper != null &&
FormatAssetLabelMethod != null &&
UpdateAssetGroupMethod != null &&
LocalizationEditorSettings_GetAddressableAssetSettingsFunc != null;
}
public static Action<SharedTableData.SharedTableEntry> Create_RaiseTableEntryModified_Method_Delegate(LocalizationEditorEvents events)
{
return (Action<SharedTableData.SharedTableEntry>) Delegate.CreateDelegate(typeof(Action<SharedTableData.SharedTableEntry>),
events,
RaiseTableEntryModified_Method);
}
public static Action<LocalizationTableCollection, SharedTableData.SharedTableEntry> Create_RaiseTableEntryAdded_Method_Delegate(
LocalizationEditorEvents events)
{
return (Action<LocalizationTableCollection, SharedTableData.SharedTableEntry>)
Delegate.CreateDelegate(typeof(Action<LocalizationTableCollection, SharedTableData.SharedTableEntry>),
events,
RaiseTableEntryAdded_Method);
}
public static Action<LocalizationTableCollection, SharedTableData.SharedTableEntry> Create_RaiseTableEntryRemoved_Method_Delegate(
LocalizationEditorEvents events)
{
return (Action<LocalizationTableCollection, SharedTableData.SharedTableEntry>)
Delegate.CreateDelegate(typeof(Action<LocalizationTableCollection, SharedTableData.SharedTableEntry>),
events,
RaiseTableEntryRemoved_Method);
}
public static Action<AssetTableCollection, AssetTable, AssetTableEntry> Create_AssetTableEntryAdded_Method_Delegate(
LocalizationEditorEvents events)
{
return (Action<AssetTableCollection, AssetTable, AssetTableEntry>)
Delegate.CreateDelegate(typeof(Action<AssetTableCollection, AssetTable, AssetTableEntry>),
events,
RaiseAssetTableEntryAdded_Method);
}
public static Action<AssetTableCollection, AssetTable, AssetTableEntry, string> Create_AssetTableEntryRemoved_Method_Delegate(
LocalizationEditorEvents events)
{
return (Action<AssetTableCollection, AssetTable, AssetTableEntry, string>)
Delegate.CreateDelegate(typeof(Action<AssetTableCollection, AssetTable, AssetTableEntry, string>),
events,
RaiseAssetTableEntryRemoved_Method);
}
public static Action<TableEntryReference, Type, string> Create_AssetTableCollection_SetEntryAssetType_PrivateMethod_Delegate(AssetTableCollection collection)
{
return (Action<TableEntryReference, Type, string>) Delegate.CreateDelegate(typeof(Action<TableEntryReference, Type, string>),
collection,
AssetTableCollection_SetEntryAssetType_PrivateMethod);
}
public static Action<TableEntryReference, string> Create_AssetTableCollection_RemoveEntryAssetType_PrivateMethod_Delegate(AssetTableCollection collection)
{
return (Action<TableEntryReference, string>) Delegate.CreateDelegate(typeof(Action<TableEntryReference, string>),
collection,
AssetTableCollection_RemoveEntryAssetType_PrivateMethod);
}
#if SIRENIX_INTERNAL
public static void EnsureInit() { }
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b54903c48e344164a8ef89c56ba21f22
timeCreated: 1704797678

View File

@@ -0,0 +1,223 @@
//-----------------------------------------------------------------------
// <copyright file="OdinLocalizationSyntaxHighlighter.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Text;
using Sirenix.OdinInspector.Modules.Localization.Editor.Configs;
using Sirenix.Utilities;
using UnityEditor;
using UnityEngine;
using UnityEngine.Localization.SmartFormat;
using UnityEngine.Localization.SmartFormat.Core.Parsing;
using UnityEngine.Localization.SmartFormat.Core.Settings;
namespace Sirenix.OdinInspector.Modules.Localization.Editor.Internal
{
public static class OdinLocalizationSyntaxHighlighter
{
private const string END_RICH_COLOR = "</color>";
private static readonly SmartFormatter Formatter;
private static readonly StringBuilder Buffer;
// TODO: if this works nicely, maybe implement a custom solution that doesn't rely on SmartFormatter
static OdinLocalizationSyntaxHighlighter()
{
Formatter = Smart.CreateDefaultSmartFormat();
Formatter.Settings.ParseErrorAction = ErrorAction.MaintainTokens;
Formatter.Settings.FormatErrorAction = ErrorAction.MaintainTokens;
Buffer = new StringBuilder();
}
public static string HighlightAsRichText(string source)
{
Format format;
try
{
format = Formatter.Parser.ParseFormat(source, Formatter.GetNotEmptyFormatterExtensionNames());
}
catch (Exception)
{
return source;
}
Buffer.Clear();
int expectedSize = source.Length;
AppendToBuffer(format, source, ref expectedSize);
// NOTE: we fallback to source in-case any obvious discrepancies are happening.
return Buffer.Length != expectedSize ? source : Buffer.ToString();
}
public static string GetErrorMessage(string source, out bool foundError, out Exception exception)
{
Formatter.Settings.ParseErrorAction = ErrorAction.OutputErrorInResult;
Formatter.Settings.FormatErrorAction = ErrorAction.OutputErrorInResult;
Format format;
try
{
format = Formatter.Parser.ParseFormat(source, Formatter.GetNotEmptyFormatterExtensionNames());
}
catch (Exception e)
{
foundError = true;
exception = e;
Debug.LogException(e);
return $"Unity Formatter threw {ObjectNames.NicifyVariableName(e.GetType().GetNiceName())}: '{e.Message}'\nCheck the console for more information.";
}
exception = null;
Buffer.Clear();
int expectedSize = source.Length;
AppendToBuffer(format, source, ref expectedSize);
foundError = expectedSize != Buffer.Length;
Formatter.Settings.ParseErrorAction = ErrorAction.MaintainTokens;
Formatter.Settings.FormatErrorAction = ErrorAction.MaintainTokens;
return Buffer.ToString();
}
private static void AppendColorAsRichTag(Color color, ref int expectedSize)
{
var color32 = (Color32) color;
var richTag = $"<color=#{color32.r:X2}{color32.g:X2}{color32.b:X2}>";
expectedSize += richTag.Length;
Buffer.Append(richTag);
}
private static bool IsLastBufferEqual(string expected)
{
if (expected.Length > Buffer.Length)
{
return false;
}
int bufferIndex = Buffer.Length - 1;
for (int i1 = expected.Length - 1; i1 >= 0; i1--, bufferIndex--)
{
if (Buffer[bufferIndex] != expected[i1])
{
return false;
}
}
return true;
}
private static void AppendToBuffer(Format format, string source, ref int expectedSize)
{
for (var i = 0; i < format.Items.Count; i++)
{
if (Buffer.Length == source.Length)
{
break;
}
FormatItem item = format.Items[i];
if (!(item is Placeholder placeholder))
{
Buffer.Append(item.RawText);
continue;
}
AppendColorAsRichTag(OdinLocalizationConfig.Instance.placeholderColor, ref expectedSize);
Buffer.Append('{');
for (var j = 0; j < placeholder.Selectors.Count; j++)
{
Selector selector = placeholder.Selectors[j];
AppendColorAsRichTag(OdinLocalizationConfig.Instance.selectorColor, ref expectedSize);
string op = selector.Operator;
if (!string.IsNullOrEmpty(op))
{
Buffer.Append(selector.Operator);
}
Buffer.Append(selector.RawText);
Buffer.Append(END_RICH_COLOR);
expectedSize += END_RICH_COLOR.Length;
}
if (placeholder.Alignment != 0)
{
Buffer.Append(',');
Buffer.Append(placeholder.Alignment);
}
if (!string.IsNullOrEmpty(placeholder.FormatterName))
{
Buffer.Append(':');
AppendColorAsRichTag(OdinLocalizationConfig.Instance.formatterColor, ref expectedSize);
Buffer.Append(placeholder.FormatterName);
if (!string.IsNullOrEmpty(placeholder.FormatterOptions))
{
Buffer.Append('(');
Buffer.Append(placeholder.FormatterOptions);
Buffer.Append(')');
}
Buffer.Append(END_RICH_COLOR);
expectedSize += END_RICH_COLOR.Length;
}
if (placeholder.Format != null)
{
Format nextFormat = placeholder.Format;
bool showColon;
if (nextFormat.startIndex > 0)
{
showColon = nextFormat.baseString[nextFormat.startIndex - 1] == ':';
}
else
{
showColon = true;
}
if (showColon)
{
Buffer.Append(':');
}
AppendToBuffer(nextFormat, source, ref expectedSize);
}
Buffer.Append('}');
Buffer.Append(END_RICH_COLOR);
expectedSize += END_RICH_COLOR.Length;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: edfba57bc4284142a39f658cc44f70c6
timeCreated: 1704797761

View File

@@ -0,0 +1,39 @@
//-----------------------------------------------------------------------
// <copyright file="LocalizationSupport.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
#if UNITY_EDITOR
namespace Sirenix.OdinInspector.Modules.Localization.Editor
{
using UnityEngine.Localization;
using Sirenix.OdinInspector.Editor;
using System;
using Sirenix.Utilities.Editor;
public class LocalizedReferenceResolver : OdinPropertyResolver<LocalizedReference>
{
public override int ChildNameToIndex(string name)
{
throw new NotSupportedException();
}
public override int ChildNameToIndex(ref StringSlice name)
{
throw new NotSupportedException();
}
public override InspectorPropertyInfo GetChildInfo(int childIndex)
{
throw new NotSupportedException();
}
protected override int GetChildCount(LocalizedReference value)
{
return 0;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 46db7b2a049564544ad19a2e480a1333
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,25 @@
//-----------------------------------------------------------------------
// <copyright file="OdinLocalizationConstants.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using Sirenix.OdinInspector.Modules.Localization.Editor.Configs;
namespace Sirenix.OdinInspector.Modules.Localization.Editor
{
public static class OdinLocalizationConstants
{
public static int AssetPreviewRowHeight => OdinLocalizationConfig.Instance.assetRowHeight;
public const int DEFAULT_COLUMN_WIDTH = 300;
public const int MIN_COLUMN_WIDTH = 200;
public const int COLUMN_HEIGHT = 38;
public const int ROW_MENU_WIDTH = 28;
public const int ROW_HEIGHT = 30;
public const int EMPTY_ASSET_ROW_HEIGHT = ROW_HEIGHT; //96;
public const int ASSET_ROW_HEIGHT = 104; //96;
public const int ENTRY_PADDING = 4;
public const int TOOLBAR_HEIGHT = 28;
public const int DRAG_HANDLE_WIDTH = 27;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cd2728f4c9e842db8d6b5e8c18cce611
timeCreated: 1706411619

View File

@@ -0,0 +1,29 @@
{
"name": "Sirenix.OdinInspector.Modules.UnityLocalization.Editor",
"references": [
"Unity.Localization",
"Sirenix.Serialization",
"Sirenix.OdinInspector.Attributes",
"Sirenix.OdinInspector.Editor",
"Sirenix.Utilities.Editor",
"Unity.Localization.Editor",
"Sirenix.Utilities",
"Unity.Addressables.Editor"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [
"Sirenix.Serialization.dll",
"Sirenix.OdinInspector.Attributes.dll",
"Sirenix.OdinInspector.Editor.dll",
"Sirenix.Utilities.Editor.dll"
],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 835e118525d8475f939f4105af066578
timeCreated: 1704796852

View File

@@ -0,0 +1,663 @@
//-----------------------------------------------------------------------
// <copyright file="OdinLocalizationEditorWindow.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Sirenix.OdinInspector.Editor;
using Sirenix.OdinInspector.Editor.Internal;
using Sirenix.OdinInspector.Modules.Localization.Editor.Configs;
using Sirenix.OdinInspector.Modules.Localization.Editor.Internal;
using Sirenix.Serialization;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using UnityEditor;
using UnityEditor.Localization;
using UnityEngine;
using UnityEngine.Localization;
namespace Sirenix.OdinInspector.Modules.Localization.Editor
{
public class OdinLocalizationCreateTableMenu
{
[Serializable]
public class LocaleItem
{
[HideInInspector]
public Locale Locale;
[HideLabel]
public bool Enabled;
}
public enum TableCollectionType
{
StringTableCollection,
AssetTableCollection
}
private string FolderPath => string.IsNullOrEmpty(this.Folder) ? "Assets" : $"Assets/{this.Folder}";
[ValidateInput(nameof(ValidateName), "@this." + nameof(nameErrorMessage))]
[VerticalGroup("Split/Left")]
[PropertySpace(SpaceAfter = 2, SpaceBefore = 2)]
public string Name;
[EnableIf("@this." + nameof(EnableFolder))]
[InfoBox("The directory is not found, this will create a new directory on creation.", visibleIfMemberName: nameof(ShowFolderInfoBox))]
[HorizontalGroup("Split")]
[VerticalGroup("Split/Left")]
[FolderPath(ParentFolder = "Assets")]
public string Folder;
[VerticalGroup("Split/Left")]
[PropertySpace(SpaceAfter = 2, SpaceBefore = 2)]
[HideLabel]
[EnumToggleButtons]
public TableCollectionType Type;
[EnableIf(nameof(EnableCreateIf))]
[VerticalGroup("Split/Left")]
[PropertySpace(SpaceBefore = 4)]
[Button(ButtonSizes.Large)]
public void Create()
{
var localizationWindow = EditorWindow.focusedWindow as OdinLocalizationEditorWindow;
if (!this.HasAnyLocaleSelected())
{
if (localizationWindow)
{
localizationWindow.ShowToast(ToastPosition.BottomLeft,
SdfIconType.ExclamationOctagonFill,
"At least 1 Locale must be selected.",
new Color(0.68f, 0.2f, 0.2f),
5.0f);
}
return;
}
if (!Directory.Exists(this.FolderPath))
{
Directory.CreateDirectory(this.FolderPath);
}
var collectionLocales = new List<Locale>(this.Locales.Count);
foreach (LocaleItem localeItem in this.Locales)
{
if (localeItem.Enabled)
{
collectionLocales.Add(localeItem.Locale);
}
}
switch (this.Type)
{
case TableCollectionType.StringTableCollection:
LocalizationEditorSettings.CreateStringTableCollection(this.Name, this.FolderPath, collectionLocales);
break;
case TableCollectionType.AssetTableCollection:
LocalizationEditorSettings.CreateAssetTableCollection(this.Name, this.FolderPath, collectionLocales);
break;
default:
throw new ArgumentOutOfRangeException();
}
if (localizationWindow)
{
string typeNiceName;
switch (this.Type)
{
case TableCollectionType.StringTableCollection:
typeNiceName = "String Table Collection";
break;
case TableCollectionType.AssetTableCollection:
typeNiceName = "Asset Table Collection";
break;
default:
throw new ArgumentOutOfRangeException();
}
localizationWindow.ShowToast(ToastPosition.BottomLeft,
SdfIconType.Check2,
$"{typeNiceName} '{this.Name}' created at: {this.FolderPath}.",
new Color(0.29f, 0.57f, 0.42f),
16.0f);
}
}
[HorizontalGroup("Split")]
[VerticalGroup("Split/Right")]
[InlineProperty]
[ListDrawerSettings(ListElementLabelName = "@this.Locale.LocaleName",
HideAddButton = true,
HideRemoveButton = true,
DefaultExpandedState = true,
ShowFoldout = false,
ShowItemCount = false,
DraggableItems = false)]
public List<LocaleItem> Locales = new List<LocaleItem>();
[HorizontalGroup("Split/Right/Split")]
[Button]
public void LocaleGenerator()
{
try
{
TwoWaySerializationBinder.Default.BindToType("UnityEditor.Localization.UI.LocaleGeneratorWindow, Unity.Localization.Editor")
.GetMethod("ShowWindow", BindingFlags.Static | BindingFlags.Public)
.Invoke(null, null);
}
catch (NullReferenceException nullReferenceException)
{
Debug.LogError($"[Odin]: Failed to find LocaleGeneratorWindow.ShowWindow.\n{nullReferenceException.Message}");
}
}
[HorizontalGroup("Split/Right/Split")]
[Button]
public void SelectNone()
{
for (var i = 0; i < this.Locales.Count; i++)
{
this.Locales[i].Enabled = false;
}
}
[HorizontalGroup("Split/Right/Split")]
[Button]
public void SelectAll()
{
for (var i = 0; i < this.Locales.Count; i++)
{
this.Locales[i].Enabled = true;
}
}
[HideInInspector]
internal bool EnableFolder = true;
private string nameErrorMessage = string.Empty;
private bool ValidateName(string name)
{
if (string.IsNullOrEmpty(name))
{
this.nameErrorMessage = $"{nameof(this.Name)} can't be empty.";
return false;
}
Type collectionType;
switch (this.Type)
{
case TableCollectionType.StringTableCollection:
collectionType = typeof(StringTableCollection);
break;
case TableCollectionType.AssetTableCollection:
collectionType = typeof(AssetTableCollection);
break;
default:
throw new ArgumentOutOfRangeException();
}
bool isTableNameValid = OdinLocalizationEditorSettings.IsTableNameValid(collectionType, name, out string localizationErrorMsg);
if (isTableNameValid)
{
return true;
}
this.nameErrorMessage = localizationErrorMsg;
return false;
}
private bool ShowFolderInfoBox()
{
if (string.IsNullOrEmpty(this.Folder))
{
return false;
}
return !Directory.Exists(this.FolderPath);
}
private bool EnableCreateIf() => this.Locales.Count > 0 && this.ValidateName(this.Name);
private bool HasAnyLocaleSelected()
{
for (var i = 0; i < this.Locales.Count; i++)
{
if (this.Locales[i].Enabled)
{
return true;
}
}
return false;
}
}
public class OdinLocalizationEditorWindow : OdinMenuEditorWindow, IDisposable
{
public enum RightMenuTopTabs
{
[LabelText(SdfIconType.Braces)]
Metadata,
[LabelText(SdfIconType.GearFill)]
Settings
}
public enum RightMenuBottomTabs
{
[LabelText(SdfIconType.FlagFill)]
Locale,
#if false
[LabelText(SdfIconType.BorderWidth)]
Template,
#endif
[LabelText(SdfIconType.GearFill)]
Settings
}
public class WindowState : IDisposable
{
public static string EditorPrefsKey = "OdinLocalizationEditorWindow_EditorPrefs";
public RightMenuTopTabs CurrentTopTab;
public RightMenuBottomTabs CurrentBottomTab;
public PropertyTree MetadataTree = null;
public bool ShowSharedMetadata = true;
public float LeftMenuWidth;
public float RightMenuWidth;
public float RightMenuTopPanelHeight;
public float LastOpenRightMenuWidth;
public void Save()
{
EditorPrefs.SetFloat($"{EditorPrefsKey}_LeftMenuWidth", this.LeftMenuWidth);
EditorPrefs.SetFloat($"{EditorPrefsKey}_RightMenuWidth", this.RightMenuWidth);
EditorPrefs.SetFloat($"{EditorPrefsKey}_RightMenuTopHeight", this.RightMenuTopPanelHeight);
EditorPrefs.SetFloat($"{EditorPrefsKey}_LastOpenRightMenuWidth", this.LastOpenRightMenuWidth);
}
public void Load()
{
this.LeftMenuWidth = EditorPrefs.GetFloat($"{EditorPrefsKey}_LeftMenuWidth", 300);
this.RightMenuWidth = EditorPrefs.GetFloat($"{EditorPrefsKey}_RightMenuWidth", 300);
this.RightMenuTopPanelHeight = EditorPrefs.GetFloat($"{EditorPrefsKey}_RightMenuTopHeight");
this.LastOpenRightMenuWidth = EditorPrefs.GetFloat($"{EditorPrefsKey}_LastOpenRightMenuWidth");
}
public void Dispose()
{
this.MetadataTree?.Dispose();
this.MetadataTree = null;
}
}
[MenuItem("Tools/Odin/Localization Editor", priority = 10_100)]
public static void OpenFromMenu()
{
var wnd = GetWindow<OdinLocalizationEditorWindow>();
wnd.MenuWidth = 300.0f;
}
public WindowState State;
private object lastSelection;
protected override void Initialize()
{
this.State = new WindowState();
this.State.Load();
}
protected override void OnDestroy()
{
base.OnDestroy();
this.DisposeActiveCollection();
this.State.Dispose();
}
protected override void OnDisable()
{
base.OnDisable();
this.State.Save();
this.DisposeActiveCollection();
this.State.Dispose();
}
protected override void OnImGUI()
{
if (LocalizationEditorSettings.ActiveLocalizationSettings == null)
{
Rect popupPosition = this.position.SetPosition(Vector2.zero).AlignCenter(360, 160);
if (EditorGUIUtility.isProSkin)
{
//OdinLocalizationGUI.DrawRoundGlowRect(popupPosition.Expand(54), FancyColor.CreateHex(0x323232));
OdinLocalizationGUI.DrawRoundBlur6(popupPosition, new Color(0, 0, 0, 0.025f));
SirenixEditorGUI.DrawRoundRect(popupPosition, FancyColor.CreateHex(0x383838), 5.0f);
}
else
{
OdinLocalizationGUI.DrawRoundBlur6(popupPosition, new Color(0, 0, 0, 0.02f));
SirenixEditorGUI.DrawRoundRect(popupPosition, new FancyColor(0.84f), 5.0f); //, new Color(0, 0, 0, 0.2f), 1);
SirenixEditorGUI.DrawRoundRect(popupPosition.AlignBottom(32 + 12 + 8 + 6), new Color(1, 1, 1, 0.2f), 0.0f, 0.0f, 5.0f, 5.0f);
}
popupPosition = popupPosition.Padding(12);
Rect buttonsArea = popupPosition.TakeFromBottom(32);
popupPosition.height -= 16;
GUIStyle labelStyle = EditorGUIUtility.isProSkin ? SirenixGUIStyles.WhiteLabelCentered : SirenixGUIStyles.BlackLabelCentered;
if (EditorGUIUtility.isProSkin)
{
GUI.Label(popupPosition, "No Localization Settings found in project.", labelStyle);
}
else
{
GUIHelper.PushColor(new Color(1, 1, 1, 0.75f));
GUI.Label(popupPosition, "No Localization Settings found in project.", SirenixGUIStyles.BlackLabelCentered);
}
if (OdinLocalizationGUI.OverlaidButton(buttonsArea.AlignCenter(120), "Create", labelStyle: labelStyle, invert: true))
{
if (OdinLocalizationEditorSettings.CreateDefaultLocalizationSettingsAsset())
{
this.ShowToast(ToastPosition.BottomLeft,
SdfIconType.GearWide,
"Default Localization Settings created.",
new Color(0.13f, 0.26f, 0.39f),
8.0f);
}
}
if (!EditorGUIUtility.isProSkin)
{
GUIHelper.PopColor();
}
this.Repaint();
return;
}
base.OnImGUI();
}
protected override OdinMenuTree BuildMenuTree()
{
var tree = new OdinMenuTree
{
Config =
{
AutoHandleKeyboardNavigation = false,
DrawSearchToolbar = true
},
DefaultMenuStyle =
{
Height = 28,
AlignTriangleLeft = true,
TrianglePadding = 0.0f
}
};
this.MenuBackgroundColor = OdinLocalizationGUI.MenuBackground;
if (LocalizationEditorSettings.ActiveLocalizationSettings == null)
{
return tree;
}
var createMenu = new OdinLocalizationCreateTableMenu();
tree.Add("Create Table", createMenu, SdfIconType.Plus);
tree.Add("User Config", OdinLocalizationConfig.Instance, SdfIconType.GearFill);
#if true
tree.Selection.SelectionChanged += type =>
{
switch (type)
{
case SelectionChangedType.ItemAdded:
if (this.lastSelection != null)
{
switch (this.lastSelection)
{
case OdinAssetTableCollectionEditor assetCollection:
{
assetCollection.DetachEvents();
break;
}
case OdinStringTableCollectionEditor stringCollection:
{
stringCollection.DetachEvents();
break;
}
}
this.State.MetadataTree?.Dispose();
this.State.MetadataTree = null;
}
switch (tree.Selection.SelectedValue)
{
case OdinAssetTableCollectionEditor assetCollection:
{
assetCollection.OnSelectInWindow();
if (assetCollection.SelectionType == OdinTableSelectionType.TableEntry && this.State.CurrentTopTab == RightMenuTopTabs.Metadata)
{
assetCollection.UpdateMetadataViewForEntry(assetCollection.CurrentSelectedEntry);
}
break;
}
case OdinStringTableCollectionEditor stringCollection:
{
stringCollection.OnSelectInWindow();
if (stringCollection.SelectionType == OdinTableSelectionType.TableEntry && this.State.CurrentTopTab == RightMenuTopTabs.Metadata)
{
stringCollection.UpdateMetadataViewForEntry(stringCollection.CurrentSelectedEntry);
}
break;
}
case OdinLocalizationCreateTableMenu createTableMenu:
createTableMenu.Locales.Clear();
foreach (Locale locale in LocalizationEditorSettings.GetLocales())
{
createTableMenu.Locales.Add(new OdinLocalizationCreateTableMenu.LocaleItem {Locale = locale, Enabled = true});
}
break;
}
this.lastSelection = this.MenuTree.Selection.SelectedValue;
break;
}
};
#endif
string[] collectionGUIDs = AssetDatabase.FindAssets($"t:{nameof(LocalizationTableCollection)}");
for (var i = 0; i < collectionGUIDs.Length; i++)
{
string assetPath = AssetDatabase.GUIDToAssetPath(collectionGUIDs[i]);
var collection = AssetDatabase.LoadAssetAtPath<LocalizationTableCollection>(assetPath);
AssetTableCollection assetTableCollection = LocalizationEditorSettings.GetAssetTableCollection(collection.TableCollectionNameReference);
if (assetTableCollection != null)
{
var guiCollection = new OdinAssetTableCollectionEditor(assetTableCollection, this, this.State);
assetPath = assetPath.Replace(".asset", string.Empty);
if (assetPath.StartsWith("Assets/"))
{
assetPath = assetPath.Remove(0, "Assets/".Length);
}
tree.Add(assetPath, guiCollection, SdfIconType.Table);
continue;
}
StringTableCollection stringTableCollection = LocalizationEditorSettings.GetStringTableCollection(collection.TableCollectionNameReference);
if (stringTableCollection != null)
{
var guiCollection = new OdinStringTableCollectionEditor(stringTableCollection, this, this.State);
assetPath = assetPath.Replace(".asset", string.Empty);
if (assetPath.StartsWith("Assets/"))
{
assetPath = assetPath.Remove(0, "Assets/".Length);
}
tree.Add(assetPath, guiCollection, SdfIconType.LayoutTextWindow);
continue;
}
}
foreach (OdinMenuItem treeMenuItem in tree.EnumerateTree())
{
if (treeMenuItem.Value != null)
{
if (treeMenuItem.Value is OdinAssetTableCollectionEditor assetEditor)
{
treeMenuItem.Name = assetEditor.Collection.SharedData.TableCollectionName;
assetEditor.MenuItem = treeMenuItem;
treeMenuItem.OnDrawItem += item =>
{
if (Event.current.OnMouseDown(item.Rect, 0, false))
{
if (Event.current.clickCount > 1)
{
EditorGUIUtility.PingObject(assetEditor.Collection);
}
}
};
continue;
}
if (treeMenuItem.Value is OdinStringTableCollectionEditor stringEditor)
{
treeMenuItem.Name = stringEditor.Collection.SharedData.TableCollectionName;
stringEditor.MenuItem = treeMenuItem;
treeMenuItem.OnDrawItem += item =>
{
if (Event.current.OnMouseDown(item.Rect, 0, false))
{
if (Event.current.clickCount > 1)
{
EditorGUIUtility.PingObject(stringEditor.Collection);
}
}
};
continue;
}
continue;
}
treeMenuItem.Value = createMenu;
treeMenuItem.SdfIcon = SdfIconType.FolderFill;
treeMenuItem.OnDrawItem += item =>
{
Rect addTableRect = item.Rect.AlignRight(20).SubX(14);
bool isMouseOver = Event.current.IsMouseOver(addTableRect);
if (EditorGUIUtility.isProSkin)
{
SdfIcons.DrawIcon(addTableRect.AlignCenter(16, 16),
SdfIconType.Plus,
isMouseOver ? new Color(1, 1, 1, 0.8f) : new Color(1, 1, 1, 0.4f));
}
else
{
SdfIcons.DrawIcon(addTableRect.AlignCenter(16, 16),
SdfIconType.Plus,
isMouseOver ? new Color(0, 0, 0, 0.8f) : new Color(0, 0, 0, 0.4f));
}
if (Event.current.OnMouseDown(item.Rect, 0, false))
{
createMenu.Folder = treeMenuItem.GetFullPath();
}
};
}
return tree;
}
public void Dispose()
{
this.DisposeActiveCollection();
this.State.Dispose();
}
private void DisposeActiveCollection()
{
if (this.MenuTree == null)
{
return;
}
switch (this.MenuTree.Selection.SelectedValue)
{
case OdinAssetTableCollectionEditor assetCollection:
assetCollection.DetachEvents();
break;
case OdinStringTableCollectionEditor stringCollection:
stringCollection.DetachEvents();
break;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a7cd6c96ce4b4c2c98fae9f89eedc690
timeCreated: 1704796865

View File

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

View File

@@ -0,0 +1,58 @@
//-----------------------------------------------------------------------
// <copyright file="LocalizedStringFormatter.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using Sirenix.Serialization;
using System.Reflection;
using UnityEngine.Localization;
using System;
#if !ODIN_INSPECTOR_EDITOR_ONLY
[assembly: RegisterFormatter(typeof(Sirenix.OdinInspector.Modules.Localization.LocalizedStringFormatter))]
#endif
namespace Sirenix.OdinInspector.Modules.Localization
{
#if !ODIN_INSPECTOR_EDITOR_ONLY
public class LocalizedStringFormatter : ReflectionOrEmittedBaseFormatter<LocalizedString>
{
private static readonly FieldInfo m_LocalVariables_Field;
static LocalizedStringFormatter()
{
m_LocalVariables_Field = typeof(LocalizedString).GetField("m_LocalVariables", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (m_LocalVariables_Field == null)
{
DefaultLoggers.DefaultLogger.LogError("Could not find field 'UnityEngine.LocalizedString.m_LocalVariables'" +
" - the internals of the Localization package have changed, and deserialization of Odin-serialized" +
" LocalizedString instances may be broken in some cases.");
}
}
protected override LocalizedString GetUninitializedObject()
{
return new LocalizedString();
}
protected override void DeserializeImplementation(ref LocalizedString value, IDataReader reader)
{
base.DeserializeImplementation(ref value, reader);
if (m_LocalVariables_Field != null && value != null)
{
var localVariablesList = m_LocalVariables_Field.GetValue(value);
// This list is not allowed to be null!
if (localVariablesList == null)
{
localVariablesList = Activator.CreateInstance(m_LocalVariables_Field.FieldType);
m_LocalVariables_Field.SetValue(value, localVariablesList);
}
}
}
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 93ff93294e9339b40b68190c779d0009
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
{
"name": "Sirenix.OdinInspector.Modules.UnityLocalization",
"references": [ "Unity.Localization", "Sirenix.Serialization", "Sirenix.Serialization.Config" ],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"autoReferenced": true,
"overrideReferences": false,
"precompiledReferences": [ "Sirenix.Serialization.dll", "Sirenix.Serialization.Config.dll" ],
"defineConstraints": [ "!ODIN_INSPECTOR_EDITOR_ONLY" ]
}

View File

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

View File

@@ -0,0 +1,62 @@
ManifestVersion: 1
ModuleID: Unity.Localization
ModuleVersion: 2.0.0.5
ModuleFiles:
Editor.meta
Runtime.meta
Editor/Configs.meta
Editor/GUI.meta
Editor/Internal.meta
Editor/LocalizationSupport.cs
Editor/LocalizationSupport.cs.meta
Editor/OdinLocalizationConstants.cs
Editor/OdinLocalizationConstants.cs.meta
Editor/Sirenix.OdinInspector.Modules.UnityLocalization.Editor.asmdef
Editor/Sirenix.OdinInspector.Modules.UnityLocalization.Editor.asmdef.meta
Editor/Windows.meta
Editor/Configs/OdinLocalizationConfig.asset
Editor/Configs/OdinLocalizationConfig.asset.meta
Editor/Configs/OdinLocalizationConfig.cs
Editor/Configs/OdinLocalizationConfig.cs.meta
Editor/GUI/LocalizationMetadata.cs
Editor/GUI/LocalizationMetadata.cs.meta
Editor/GUI/OdinAssetTableCollectionEditor.cs
Editor/GUI/OdinAssetTableCollectionEditor.cs.meta
Editor/GUI/OdinGUITable.cs
Editor/GUI/OdinGUITable.cs.meta
Editor/GUI/OdinGUITableCollection.cs
Editor/GUI/OdinGUITableCollection.cs.meta
Editor/GUI/OdinSharedEntryCollection.cs
Editor/GUI/OdinSharedEntryCollection.cs.meta
Editor/GUI/OdinStringTableCollectionEditor.cs
Editor/GUI/OdinStringTableCollectionEditor.cs.meta
Editor/GUI/OdinTableCollectionEditor.cs
Editor/GUI/OdinTableCollectionEditor.cs.meta
Editor/GUI/OdinTemplateMetadata.cs
Editor/GUI/OdinTemplateMetadata.cs.meta
Editor/GUI/Toggle.cs
Editor/GUI/Toggle.cs.meta
Editor/Internal/OdinLocalizationAssetCache.cs
Editor/Internal/OdinLocalizationAssetCache.cs.meta
Editor/Internal/OdinLocalizationEditorSettings.cs
Editor/Internal/OdinLocalizationEditorSettings.cs.meta
Editor/Internal/OdinLocalizationEvents.cs
Editor/Internal/OdinLocalizationEvents.cs.meta
Editor/Internal/OdinLocalizationGUI.cs
Editor/Internal/OdinLocalizationGUI.cs.meta
Editor/Internal/OdinLocalizationGUITextures.cs
Editor/Internal/OdinLocalizationGUITextures.cs.meta
Editor/Internal/OdinLocalizationMetadataRegistry.cs
Editor/Internal/OdinLocalizationMetadataRegistry.cs.meta
Editor/Internal/OdinLocalizationReflectionValidator.cs
Editor/Internal/OdinLocalizationReflectionValidator.cs.meta
Editor/Internal/OdinLocalizationReflectionValues.cs
Editor/Internal/OdinLocalizationReflectionValues.cs.meta
Editor/Internal/OdinLocalizationSyntaxHighlighter.cs
Editor/Internal/OdinLocalizationSyntaxHighlighter.cs.meta
Editor/Windows/OdinLocalizationEditorWindow.cs
Editor/Windows/OdinLocalizationEditorWindow.cs.meta
Runtime/LocalizedStringFormatter.cs
Runtime/LocalizedStringFormatter.cs.meta
Runtime/Sirenix.OdinInspector.Modules.UnityLocalization.asmdef
Runtime/Sirenix.OdinInspector.Modules.UnityLocalization.asmdef.meta

View File

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