diff --git a/Editor/Configurator.meta b/Editor/Configurator.meta
new file mode 100644
index 00000000..db078275
--- /dev/null
+++ b/Editor/Configurator.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 54100430fca943e46a942410509a2543
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Avatar.meta b/Editor/Configurator/Avatar.meta
new file mode 100644
index 00000000..aff2ddce
--- /dev/null
+++ b/Editor/Configurator/Avatar.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 49af73e30aa550e42aae80796afe4039
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Avatar/AvatarUtils.cs b/Editor/Configurator/Avatar/AvatarUtils.cs
new file mode 100644
index 00000000..ef74469c
--- /dev/null
+++ b/Editor/Configurator/Avatar/AvatarUtils.cs
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System.Collections.Generic;
+using Chocopoi.DressingFramework;
+using Chocopoi.DressingTools.Components.Cabinet;
+using Chocopoi.DressingTools.Components.OneConf;
+using Chocopoi.DressingTools.Configurator.Cabinet;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+namespace Chocopoi.DressingTools.Configurator.Avatar
+{
+ internal static class AvatarUtils
+ {
+ public static List FindSceneAvatars(Scene scene)
+ {
+ return DKRuntimeUtils.FindSceneAvatars(scene);
+ }
+
+ public static GameObject GetAvatarRoot(GameObject gameObject)
+ {
+ return DKRuntimeUtils.GetAvatarRoot(gameObject);
+ }
+
+ public static IAvatarSettings GetAvatarSettings(GameObject avatarGameObject)
+ {
+ if (avatarGameObject == null)
+ {
+ return null;
+ }
+
+ if (avatarGameObject.TryGetComponent(out _))
+ {
+ return new OneConfAvatarSettings(avatarGameObject);
+ }
+ // TODO: standalone avatar settings component
+ return new OneConfAvatarSettings(avatarGameObject);
+ }
+
+ public static IWardrobeProvider GetWardrobeProvider(GameObject avatarGameObject)
+ {
+ if (avatarGameObject == null)
+ {
+ return null;
+ }
+
+ if (avatarGameObject.TryGetComponent(out _))
+ {
+ return new OneConfCabinetProvider(avatarGameObject);
+ }
+ // if (avatarGameObject.TryGetComponent(out _))
+ // {
+ // return new DTWardrobeProvider(avatarGameObject);
+ // }
+ return new OneConfCabinetProvider(avatarGameObject);
+ }
+ }
+}
diff --git a/Tests~/Editor/UI/Presenters/DressingPresenterTest.cs.meta b/Editor/Configurator/Avatar/AvatarUtils.cs.meta
similarity index 83%
rename from Tests~/Editor/UI/Presenters/DressingPresenterTest.cs.meta
rename to Editor/Configurator/Avatar/AvatarUtils.cs.meta
index 3dce1765..50c651c5 100644
--- a/Tests~/Editor/UI/Presenters/DressingPresenterTest.cs.meta
+++ b/Editor/Configurator/Avatar/AvatarUtils.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: f2e152828377d2c499af2ca00f7fdbb7
+guid: 2929f0fb8421b5b4bb28cb6a08500a78
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Editor/Configurator/Avatar/IAvatarSettings.cs b/Editor/Configurator/Avatar/IAvatarSettings.cs
new file mode 100644
index 00000000..40d4fdf2
--- /dev/null
+++ b/Editor/Configurator/Avatar/IAvatarSettings.cs
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using UnityEngine;
+
+namespace Chocopoi.DressingTools.Configurator.Avatar
+{
+ internal enum WriteDefaultsModes
+ {
+ Auto = 0,
+ On = 1,
+ Off = 2
+ }
+
+ internal interface IAvatarSettings
+ {
+ WriteDefaultsModes WriteDefaultsMode { get; set; }
+ }
+}
diff --git a/Editor/Configurator/Avatar/IAvatarSettings.cs.meta b/Editor/Configurator/Avatar/IAvatarSettings.cs.meta
new file mode 100644
index 00000000..a7e34d00
--- /dev/null
+++ b/Editor/Configurator/Avatar/IAvatarSettings.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 190df016052766240b40cfd53fd01fe0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Avatar/OneConfAvatarSettings.cs b/Editor/Configurator/Avatar/OneConfAvatarSettings.cs
new file mode 100644
index 00000000..3884dc1d
--- /dev/null
+++ b/Editor/Configurator/Avatar/OneConfAvatarSettings.cs
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System;
+using Chocopoi.DressingTools.Components.OneConf;
+using Chocopoi.DressingTools.OneConf.Cabinet;
+using Chocopoi.DressingTools.OneConf.Serialization;
+using UnityEngine;
+
+namespace Chocopoi.DressingTools.Configurator.Avatar
+{
+ internal class OneConfAvatarSettings : IAvatarSettings
+ {
+ public WriteDefaultsModes WriteDefaultsMode
+ {
+ get
+ {
+ ReadCabinetConfig(out _, out var config);
+ // TODO: should explicit convert them one by one
+ return (WriteDefaultsModes)config.animationWriteDefaultsMode;
+ }
+ set
+ {
+ WriteCabinetConfig((comp, config) =>
+ {
+ // TODO: should explicit convert them one by one
+ config.animationWriteDefaultsMode = (CabinetConfig.WriteDefaultsMode)value;
+ });
+ }
+ }
+
+ private readonly GameObject _avatarGameObject;
+
+ public OneConfAvatarSettings(GameObject avatarGameObject)
+ {
+ _avatarGameObject = avatarGameObject;
+ }
+
+ private void ReadCabinetConfig(out DTCabinet comp, out CabinetConfig config)
+ {
+ if (_avatarGameObject.TryGetComponent(out comp))
+ {
+ if (!CabinetConfigUtility.TryDeserialize(comp.ConfigJson, out config))
+ {
+ config = new CabinetConfig();
+ }
+ }
+ else
+ {
+ comp = null;
+ config = new CabinetConfig();
+ }
+ }
+
+ private void WriteCabinetConfig(Action func)
+ {
+ CabinetConfig config;
+ if (_avatarGameObject.TryGetComponent(out var cabinetComp))
+ {
+ if (!CabinetConfigUtility.TryDeserialize(cabinetComp.ConfigJson, out config))
+ {
+ config = new CabinetConfig();
+ }
+ }
+ else
+ {
+ cabinetComp = _avatarGameObject.AddComponent();
+ config = new CabinetConfig();
+ }
+ func?.Invoke(cabinetComp, config);
+ cabinetComp.ConfigJson = CabinetConfigUtility.Serialize(config);
+ }
+ }
+}
diff --git a/Editor/Configurator/Avatar/OneConfAvatarSettings.cs.meta b/Editor/Configurator/Avatar/OneConfAvatarSettings.cs.meta
new file mode 100644
index 00000000..95a86393
--- /dev/null
+++ b/Editor/Configurator/Avatar/OneConfAvatarSettings.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ae375916b7bc14648aa69e2399f3532d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/AvatarPreviewUtility.cs b/Editor/Configurator/AvatarPreviewUtility.cs
new file mode 100644
index 00000000..324a9444
--- /dev/null
+++ b/Editor/Configurator/AvatarPreviewUtility.cs
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System;
+using Chocopoi.DressingFramework;
+using Chocopoi.DressingTools.Configurator.Cabinet;
+using UnityEditor.SceneManagement;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+namespace Chocopoi.DressingTools.Configurator
+{
+ internal static class AvatarPreviewUtility
+ {
+#if UNITY_2020_1_OR_NEWER
+ private class AvatarPreviewStage : PreviewSceneStage
+ {
+ public GameObject avatarGameObject;
+ public IConfigurableOutfit outfit;
+
+ private GameObject _previewAvatarGameObject;
+ private GameObject _previewOutfitGameObject;
+
+ protected override GUIContent CreateHeaderContent()
+ {
+ return new GUIContent("DressingTools Preview");
+ }
+
+ private void PrepareAvatarPreview()
+ {
+ var lighting = new GameObject("Lighting");
+ lighting.AddComponent().type = LightType.Directional;
+ SceneManager.MoveGameObjectToScene(lighting, scene);
+
+ _previewAvatarGameObject = Instantiate(avatarGameObject);
+ _previewAvatarGameObject.name = avatarGameObject.name;
+ if (DKEditorUtils.IsGrandParent(_previewAvatarGameObject.transform, outfit.RootTransform))
+ {
+ // if it's inside, reuse it
+ var path = DKEditorUtils.GetRelativePath(outfit.RootTransform, _previewAvatarGameObject.transform);
+ _previewOutfitGameObject = _previewAvatarGameObject.transform.Find(path).gameObject;
+ }
+ else
+ {
+ // if it's outside, create a new one and set parent
+ _previewOutfitGameObject = Instantiate(outfit.RootTransform.gameObject);
+ _previewOutfitGameObject.name = outfit.RootTransform.name;
+ _previewOutfitGameObject.transform.SetParent(_previewAvatarGameObject.transform, false);
+ }
+ outfit.Preview(_previewAvatarGameObject, _previewOutfitGameObject);
+ SceneManager.MoveGameObjectToScene(_previewAvatarGameObject, scene);
+ }
+
+ protected override bool OnOpenStage()
+ {
+ base.OnOpenStage();
+ scene = EditorSceneManager.NewPreviewScene();
+ PrepareAvatarPreview();
+ return true;
+ }
+
+ protected override void OnCloseStage()
+ {
+ foreach (var go in scene.GetRootGameObjects())
+ {
+ DestroyImmediate(go);
+ }
+ EditorSceneManager.ClosePreviewScene(scene);
+ base.OnCloseStage();
+ }
+ }
+#endif
+
+ public static void StartAvatarPreview(GameObject avatarGameObject, IConfigurableOutfit outfit, bool legacy = false)
+ {
+#if UNITY_2020_1_OR_NEWER
+ if (!legacy)
+ {
+ var stage = ScriptableObject.CreateInstance();
+ stage.avatarGameObject = avatarGameObject;
+ stage.outfit = outfit;
+ StageUtility.GoToStage(stage, true);
+ return;
+ }
+#endif
+ throw new NotImplementedException("Legacy preview is not yet implemented.");
+ }
+ }
+}
diff --git a/Editor/Configurator/AvatarPreviewUtility.cs.meta b/Editor/Configurator/AvatarPreviewUtility.cs.meta
new file mode 100644
index 00000000..9bf28d8e
--- /dev/null
+++ b/Editor/Configurator/AvatarPreviewUtility.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f495cab911e96f8489ae4ec21e4f7c60
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Cabinet.meta b/Editor/Configurator/Cabinet.meta
new file mode 100644
index 00000000..f70e2df6
--- /dev/null
+++ b/Editor/Configurator/Cabinet.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 835453005c4ccea42a22471323239648
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Cabinet/IConfigurableOutfit.cs b/Editor/Configurator/Cabinet/IConfigurableOutfit.cs
new file mode 100644
index 00000000..ae2b4f69
--- /dev/null
+++ b/Editor/Configurator/Cabinet/IConfigurableOutfit.cs
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+// using System.Collections.Generic;
+// using Chocopoi.DressingTools.Configurator.Modules;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Cabinet
+{
+ internal interface IConfigurableOutfit
+ {
+ Transform RootTransform { get; }
+ string Name { get; }
+ Texture2D Icon { get; }
+
+ // List GetModules();
+ // VisualElement CreateView();
+ void Preview(GameObject previewAvatarGameObject, GameObject previewOutfitGameObject);
+ }
+}
diff --git a/Editor/Configurator/Cabinet/IConfigurableOutfit.cs.meta b/Editor/Configurator/Cabinet/IConfigurableOutfit.cs.meta
new file mode 100644
index 00000000..c7e8f444
--- /dev/null
+++ b/Editor/Configurator/Cabinet/IConfigurableOutfit.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 747be604a05b06b49a4f07ccd5743fa0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Cabinet/IWardrobeProvider.cs b/Editor/Configurator/Cabinet/IWardrobeProvider.cs
new file mode 100644
index 00000000..4942436f
--- /dev/null
+++ b/Editor/Configurator/Cabinet/IWardrobeProvider.cs
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Cabinet
+{
+ internal interface IWardrobeProvider
+ {
+ List GetOutfits();
+ void RemoveOutfit(IConfigurableOutfit outfit);
+ VisualElement CreateView();
+ }
+}
diff --git a/Editor/Configurator/Cabinet/IWardrobeProvider.cs.meta b/Editor/Configurator/Cabinet/IWardrobeProvider.cs.meta
new file mode 100644
index 00000000..635ecde4
--- /dev/null
+++ b/Editor/Configurator/Cabinet/IWardrobeProvider.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 27d0b049760deac419a346f76d20d67a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Cabinet/OneConfCabinetProvider.cs b/Editor/Configurator/Cabinet/OneConfCabinetProvider.cs
new file mode 100644
index 00000000..dc3bcd7c
--- /dev/null
+++ b/Editor/Configurator/Cabinet/OneConfCabinetProvider.cs
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System.Collections.Generic;
+using Chocopoi.DressingTools.Localization;
+using Chocopoi.DressingFramework.Localization;
+using Chocopoi.DressingTools.OneConf;
+using UnityEngine;
+using UnityEngine.UIElements;
+using UnityEditor;
+using Chocopoi.DressingTools.OneConf.Serialization;
+using Chocopoi.DressingTools.OneConf.Cabinet.Modules.BuiltIn;
+using Chocopoi.DressingTools.OneConf.Cabinet;
+
+namespace Chocopoi.DressingTools.Configurator.Cabinet
+{
+ internal class OneConfCabinetProvider : IWardrobeProvider
+ {
+ private static readonly I18nTranslator t = I18n.ToolTranslator;
+
+ private readonly GameObject _avatarGameObject;
+
+ public OneConfCabinetProvider(GameObject avatarGameObject)
+ {
+ _avatarGameObject = avatarGameObject;
+ }
+
+ public VisualElement CreateView()
+ {
+ var container = new VisualElement();
+
+ var cabinet = OneConfUtils.GetAvatarCabinet(_avatarGameObject);
+ if (cabinet == null || !CabinetConfigUtility.TryDeserialize(cabinet.ConfigJson, out var config))
+ {
+ // TODO: show message
+ return container;
+ }
+
+ var armatureNameField = new TextField(t._("editor.main.avatar.settings.oneConf.textField.avatarArmatureName")) { value = config.avatarArmatureName };
+ container.Add(armatureNameField);
+ var groupDynToggle = new Toggle(t._("editor.main.avatar.settings.oneConf.toggle.groupDynamics")) { value = config.groupDynamics };
+ container.Add(groupDynToggle);
+ var groupDynSepToggle = new Toggle(t._("editor.main.avatar.settings.oneConf.toggle.groupDynamicsSeparate")) { value = config.groupDynamicsSeparateGameObjects };
+ container.Add(groupDynSepToggle);
+
+ var cabAnimConfig = config.FindModuleConfig();
+ if (cabAnimConfig == null)
+ {
+ cabAnimConfig = new CabinetAnimCabinetModuleConfig();
+ config.modules.Add(new CabinetModule()
+ {
+ config = cabAnimConfig,
+ moduleName = CabinetAnimCabinetModuleConfig.ModuleIdentifier
+ });
+ }
+
+ var useThumbnailsToggle = new Toggle(t._("editor.main.avatar.settings.oneConf.toggle.useThumbnailsAsMenuIcons")) { value = cabAnimConfig.thumbnails };
+ container.Add(useThumbnailsToggle);
+ var resetCustomizablesOnSwitch = new Toggle(t._("editor.main.avatar.settings.oneConf.toggle.resetCustomizablesOnSwitch")) { value = cabAnimConfig.resetCustomizablesOnSwitch };
+ container.Add(resetCustomizablesOnSwitch);
+
+ container.Add(new IMGUIContainer(() => EditorGUILayout.HelpBox(t._("editor.main.avatar.settings.oneConf.helpbox.installPathDescription"), MessageType.Info)));
+
+ var installPathField = new TextField(t._("editor.main.avatar.settings.oneConf.textField.menuInstallPath")) { value = cabAnimConfig.menuInstallPath };
+ container.Add(installPathField);
+ var itemNameField = new TextField(t._("editor.main.avatar.settings.oneConf.textField.menuItemName")) { value = cabAnimConfig.menuItemName };
+ container.Add(itemNameField);
+
+ var networkSyncedToggle = new Toggle(t._("editor.main.avatar.settings.oneConf.toggle.networkSynced")) { value = cabAnimConfig.networkSynced };
+ container.Add(networkSyncedToggle);
+ var savedToggle = new Toggle(t._("editor.main.avatar.settings.oneConf.toggle.saved")) { value = cabAnimConfig.saved };
+ container.Add(savedToggle);
+
+ return container;
+ }
+
+ public List GetOutfits()
+ {
+ var wearables = OneConfUtils.GetCabinetWearables(_avatarGameObject);
+ var outfits = new List();
+ foreach (var wearable in wearables)
+ {
+ outfits.Add(new OneConfConfigurableOutfit(_avatarGameObject, wearable));
+ }
+ return outfits;
+ }
+
+ public void RemoveOutfit(IConfigurableOutfit outfit)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/Editor/Configurator/Cabinet/OneConfCabinetProvider.cs.meta b/Editor/Configurator/Cabinet/OneConfCabinetProvider.cs.meta
new file mode 100644
index 00000000..f5cb62bc
--- /dev/null
+++ b/Editor/Configurator/Cabinet/OneConfCabinetProvider.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c1c6be9fd8672fb48be304cc5ac7780c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Configurator/Cabinet/OneConfConfigurableOutfit.cs b/Editor/Configurator/Cabinet/OneConfConfigurableOutfit.cs
new file mode 100644
index 00000000..bf94356b
--- /dev/null
+++ b/Editor/Configurator/Cabinet/OneConfConfigurableOutfit.cs
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2024 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingFramework. If not, see .
+ */
+
+using System.Collections.ObjectModel;
+using Chocopoi.DressingFramework.Components;
+using Chocopoi.DressingFramework.Detail.DK;
+using Chocopoi.DressingTools.Components.OneConf;
+// using Chocopoi.DressingTools.Configurator.Modules;
+using Chocopoi.DressingTools.Dynamics;
+using Chocopoi.DressingTools.OneConf;
+using Chocopoi.DressingTools.OneConf.Serialization;
+using Chocopoi.DressingTools.OneConf.Wearable.Modules;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.Configurator.Cabinet
+{
+ internal class OneConfConfigurableOutfit : IConfigurableOutfit
+ {
+ public Transform RootTransform
+ {
+ get => _wearableComp.RootGameObject.transform;
+ }
+ public string Name
+ {
+ get => WearableConfigUtility.TryDeserialize(_wearableComp.ConfigJson, out var config) ?
+ config.info.name :
+ "(Error)";
+
+ }
+ public Texture2D Icon
+ {
+ get => WearableConfigUtility.TryDeserialize(_wearableComp.ConfigJson, out var config) ?
+ (string.IsNullOrEmpty(config.info.thumbnail) ?
+ null :
+ OneConfUtils.GetTextureFromBase64(config.info.thumbnail)) :
+ null;
+ }
+
+ private readonly GameObject _avatarGameObject;
+ private readonly DTWearable _wearableComp;
+
+ public OneConfConfigurableOutfit(GameObject avatarGameObject, DTWearable wearableComp)
+ {
+ _avatarGameObject = avatarGameObject;
+ _wearableComp = wearableComp;
+ }
+
+ // public VisualElement CreateView()
+ // {
+ // throw new System.NotImplementedException();
+ // }
+
+ // public List GetModules()
+ // {
+ // var modules = new List();
+ // if (!WearableConfigUtility.TryDeserialize(_wearableComp.ConfigJson, out var config))
+ // {
+ // Debug.LogError("[DressingTools] Unable to deserialize OneConf wearable config, returning empty modules");
+ // return modules;
+ // }
+
+ // foreach (var oneConfModule in config.modules)
+ // {
+ // // TODO
+ // if (oneConfModule.moduleName == ArmatureMappingWearableModuleConfig.ModuleIdentifier &&
+ // oneConfModule.config is ArmatureMappingWearableModuleConfig)
+ // {
+ // modules.Add(new OneConfArmatureMappingModule(_avatarGameObject, _wearableComp));
+ // }
+ // }
+
+ // return modules;
+ // }
+
+ public void Preview(GameObject previewAvatarGameObject, GameObject previewOutfitGameObject)
+ {
+ if (previewAvatarGameObject == null || previewOutfitGameObject == null)
+ {
+ return;
+ }
+
+ var cabinet = OneConfUtils.GetAvatarCabinet(previewAvatarGameObject);
+ if (cabinet == null)
+ {
+ return;
+ }
+
+ if (!CabinetConfigUtility.TryDeserialize(cabinet.ConfigJson, out var cabinetConfig))
+ {
+ Debug.LogError("[DressingTools] Unable to deserialize cabinet config for preview");
+ return;
+ }
+
+ if (!WearableConfigUtility.TryDeserialize(_wearableComp.ConfigJson, out var wearableConfig))
+ {
+ Debug.LogError("[DressingTools] Unable to deserialize OneConf wearable config for preview");
+ return;
+ }
+
+ var dkCtx = new DKNativeContext(previewAvatarGameObject);
+ var cabCtx = new CabinetContext
+ {
+ dkCtx = dkCtx,
+ cabinetConfig = cabinetConfig
+ };
+
+ var wearCtx = new WearableContext
+ {
+ wearableConfig = wearableConfig,
+ wearableGameObject = previewOutfitGameObject,
+ wearableDynamics = DynamicsUtils.ScanDynamics(previewOutfitGameObject)
+ };
+
+ var providers = ModuleManager.Instance.GetAllWearableModuleProviders();
+
+ foreach (var provider in providers)
+ {
+ if (!provider.Invoke(cabCtx, wearCtx, new ReadOnlyCollection(wearableConfig.FindModules(provider.Identifier)), true))
+ {
+ Debug.LogError("[DressingTools] Error applying wearable in preview!");
+ break;
+ }
+ }
+
+ // remove all DK components
+ var dkComps = previewAvatarGameObject.GetComponentsInChildren();
+ foreach (var comp in dkComps)
+ {
+ Object.DestroyImmediate(comp);
+ }
+ }
+ }
+}
diff --git a/Editor/Configurator/Cabinet/OneConfConfigurableOutfit.cs.meta b/Editor/Configurator/Cabinet/OneConfConfigurableOutfit.cs.meta
new file mode 100644
index 00000000..2f4831d6
--- /dev/null
+++ b/Editor/Configurator/Cabinet/OneConfConfigurableOutfit.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 40b11c7d165facb4a9d3f805dc964276
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Inspector/DTCabinetEditor.cs b/Editor/Inspector/DTCabinetEditor.cs
index 08dab8dd..83dda1c5 100644
--- a/Editor/Inspector/DTCabinetEditor.cs
+++ b/Editor/Inspector/DTCabinetEditor.cs
@@ -46,7 +46,7 @@ public override void OnInspectorGUI()
var window = (DTMainEditorWindow)EditorWindow.GetWindow(typeof(DTMainEditorWindow));
window.titleContent = new GUIContent(t._("tool.name"));
window.Show();
- window.SelectCabinet(cabinet);
+ window.SelectAvatar(cabinet.RootGameObject);
}
}
}
diff --git a/Editor/Inspector/DTWearableEditor.cs b/Editor/Inspector/DTWearableEditor.cs
index f9e1a4aa..31703958 100644
--- a/Editor/Inspector/DTWearableEditor.cs
+++ b/Editor/Inspector/DTWearableEditor.cs
@@ -50,7 +50,7 @@ public override void OnInspectorGUI()
var window = (DTMainEditorWindow)EditorWindow.GetWindow(typeof(DTMainEditorWindow));
window.titleContent = new GUIContent(t._("tool.name"));
window.Show();
- window.StartDressing(cabinet.RootGameObject, wearable.RootGameObject);
+ window.StartDressing(wearable.RootGameObject, cabinet.RootGameObject);
}
else
{
diff --git a/Editor/UI/DTMainEditorWindow.cs b/Editor/UI/DTMainEditorWindow.cs
index d74fd95e..f03ce521 100644
--- a/Editor/UI/DTMainEditorWindow.cs
+++ b/Editor/UI/DTMainEditorWindow.cs
@@ -15,13 +15,16 @@
* You should have received a copy of the GNU General Public License along with DressingTools. If not, see .
*/
+using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
using Chocopoi.DressingFramework.Localization;
-using Chocopoi.DressingTools.Components.OneConf;
using Chocopoi.DressingTools.Localization;
using Chocopoi.DressingTools.UI.Views;
using UnityEditor;
using UnityEngine;
+using UnityEngine.UIElements;
namespace Chocopoi.DressingTools.UI
{
@@ -29,8 +32,12 @@ namespace Chocopoi.DressingTools.UI
internal class DTMainEditorWindow : EditorWindow
{
private static readonly I18nTranslator t = I18n.ToolTranslator;
+ private static string[] s_availableLocales = null;
+ private static List s_localeChoices = null;
private MainView _view;
+ private PopupField _languagePopup;
+
[MenuItem("Tools/chocopoi/Reload Translations", false, 0)]
public static void ReloadTranslations()
@@ -46,19 +53,72 @@ public static void ShowWindow()
window.Show();
}
- public void SelectCabinet(DTCabinet cabinet) => _view.SelectCabinet(cabinet);
+ public void SelectAvatar(GameObject avatarGameObject) => _view.SelectAvatar(avatarGameObject);
- public void StartDressing(GameObject avatarGameObject, GameObject wearableGameObject)
+ public void StartDressing(GameObject targetOutfit, GameObject targetAvatar)
{
_view.SelectedTab = 1;
- _view.StartDressing(avatarGameObject, wearableGameObject);
+ _view.StartDressing(targetOutfit, targetAvatar);
}
- public void OnEnable()
+ private void AddLanguagePopup()
+ {
+ if (s_availableLocales == null || s_localeChoices == null)
+ {
+ s_availableLocales = t.GetAvailableLocales();
+ s_localeChoices = new List();
+ foreach (var locale in s_availableLocales)
+ {
+ s_localeChoices.Add(new CultureInfo(locale).NativeName);
+ }
+ }
+
+ var langIndex = Array.IndexOf(s_availableLocales, PreferencesUtility.GetPreferences().app.selectedLanguage);
+ if (langIndex == -1)
+ {
+ langIndex = 0;
+ }
+
+ _languagePopup = new PopupField(s_localeChoices, langIndex);
+ _languagePopup.style.position = Position.Absolute;
+ _languagePopup.style.top = 0;
+ _languagePopup.style.right = 0;
+ _languagePopup.RegisterValueChangedCallback((ChangeEvent evt) =>
+ {
+ var locale = s_availableLocales[_languagePopup.index];
+ PreferencesUtility.GetPreferences().app.selectedLanguage = locale;
+ I18nManager.Instance.SetLocale(locale);
+ PreferencesUtility.SavePreferences();
+ CreateNewView();
+ });
+ rootVisualElement.Add(_languagePopup);
+ }
+
+ private void CleanUp()
{
+ if (_view != null)
+ {
+ _view.OnDisable();
+ if (rootVisualElement.Contains(_view))
+ {
+ rootVisualElement.Remove(_view);
+ }
+ _view = null;
+ }
+ }
+
+ private void CreateNewView()
+ {
+ CleanUp();
_view = new MainView();
rootVisualElement.Add(_view);
_view.OnEnable();
+ AddLanguagePopup();
+ }
+
+ public void OnEnable()
+ {
+ CreateNewView();
}
public void OnDisable()
diff --git a/Editor/UI/GameObjectMenu.cs b/Editor/UI/GameObjectMenu.cs
index f8947993..6c50b229 100644
--- a/Editor/UI/GameObjectMenu.cs
+++ b/Editor/UI/GameObjectMenu.cs
@@ -169,10 +169,10 @@ public static void StartSetupWizard(MenuCommand menuCommand)
return;
}
- var wearable = (GameObject)menuCommand.context;
+ var outfitGameObject = (GameObject)menuCommand.context;
// find the avatar
- var cabinet = LookUpCabinet(wearable.transform);
+ var cabinet = LookUpCabinet(outfitGameObject.transform);
if (cabinet == null)
{
@@ -184,7 +184,7 @@ public static void StartSetupWizard(MenuCommand menuCommand)
var window = (DTMainEditorWindow)EditorWindow.GetWindow(typeof(DTMainEditorWindow));
window.titleContent = new GUIContent(t._("tool.name"));
window.Show();
- window.StartDressing(cabinet.gameObject, wearable);
+ window.StartDressing(outfitGameObject, cabinet.gameObject);
}
}
}
diff --git a/Editor/UI/Presenters/AvatarPresenter.cs b/Editor/UI/Presenters/AvatarPresenter.cs
new file mode 100644
index 00000000..add05dbe
--- /dev/null
+++ b/Editor/UI/Presenters/AvatarPresenter.cs
@@ -0,0 +1,156 @@
+/*
+ * File: CabinetPresenter.cs
+ * Project: DressingTools
+ * Created Date: Wednesday, August 9th 2023, 11:38:52 pm
+ * Author: chocopoi (poi@chocopoi.com)
+ * -----
+ * Copyright (c) 2023 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingTools. If not, see .
+ */
+
+using System.Collections.Generic;
+using Chocopoi.DressingFramework.Localization;
+using Chocopoi.DressingTools.Configurator.Avatar;
+using Chocopoi.DressingTools.Localization;
+using Chocopoi.DressingTools.UI.Views;
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+namespace Chocopoi.DressingTools.UI.Presenters
+{
+ internal class AvatarPresenter
+ {
+ private static readonly I18nTranslator t = I18n.ToolTranslator;
+
+ private IAvatarSubView _view;
+
+ public AvatarPresenter(IAvatarSubView view)
+ {
+ _view = view;
+
+ SubscribeEvents();
+ }
+
+ private void SubscribeEvents()
+ {
+ _view.Load += OnLoad;
+ _view.Unload += OnUnload;
+
+ _view.AddOutfitButtonClick += OnAddOutfitButtonClick;
+ _view.ForceUpdateView += OnForceUpdateView;
+ _view.AvatarSettingsChange += OnAvatarSettingsChange;
+
+ EditorApplication.hierarchyChanged += OnHierarchyChanged;
+ EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
+ }
+
+ private void UnsubscribeEvents()
+ {
+ _view.Load -= OnLoad;
+ _view.Unload -= OnUnload;
+
+ _view.AddOutfitButtonClick -= OnAddOutfitButtonClick;
+ _view.ForceUpdateView -= OnForceUpdateView;
+ _view.AvatarSettingsChange -= OnAvatarSettingsChange;
+
+ EditorApplication.hierarchyChanged -= OnHierarchyChanged;
+ EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
+ }
+
+ private void OnPlayModeStateChanged(PlayModeStateChange change)
+ {
+ if (change == PlayModeStateChange.EnteredEditMode)
+ {
+ UpdateView();
+ }
+ }
+
+ private void OnHierarchyChanged()
+ {
+ if (Application.isPlaying) return;
+ UpdateView();
+ }
+
+ private void OnAvatarSettingsChange()
+ {
+ var settings = AvatarUtils.GetAvatarSettings(_view.SelectedAvatarGameObject);
+ if (settings == null)
+ {
+ return;
+ }
+
+ // TODO: explicitly convert one by one
+ settings.WriteDefaultsMode = (WriteDefaultsModes)_view.SettingsAnimationWriteDefaultsMode;
+ }
+
+ private void OnForceUpdateView()
+ {
+ UpdateView();
+ }
+
+ private void UpdateAvatarContentView()
+ {
+ _view.InstalledOutfitPreviews.Clear();
+
+ // TODO: explicitly convert one by one
+ var settings = AvatarUtils.GetAvatarSettings(_view.SelectedAvatarGameObject);
+ if (settings != null)
+ {
+ _view.SettingsAnimationWriteDefaultsMode = (int)settings.WriteDefaultsMode;
+ }
+
+ var wardrobe = AvatarUtils.GetWardrobeProvider(_view.SelectedAvatarGameObject);
+ if (wardrobe != null)
+ {
+ var outfits = wardrobe.GetOutfits();
+
+ foreach (var outfit in outfits)
+ {
+ _view.InstalledOutfitPreviews.Add(new OutfitPreview()
+ {
+ name = outfit.Name,
+ thumbnail = outfit.Icon,
+ RemoveButtonClick = () =>
+ {
+ wardrobe.RemoveOutfit(outfit);
+ UpdateView();
+ },
+ EditButtonClick = () =>
+ {
+ _view.StartDressing(outfit.RootTransform.gameObject, _view.SelectedAvatarGameObject);
+ }
+ });
+ }
+ }
+ }
+
+ private void UpdateView()
+ {
+ UpdateAvatarContentView();
+ _view.Repaint();
+ }
+
+ private void OnLoad()
+ {
+ UpdateView();
+ }
+
+ private void OnUnload()
+ {
+ UnsubscribeEvents();
+ }
+
+ private void OnAddOutfitButtonClick()
+ {
+ _view.StartDressing();
+ }
+ }
+}
diff --git a/Editor/UI/Presenters/CabinetPresenter.cs.meta b/Editor/UI/Presenters/AvatarPresenter.cs.meta
similarity index 100%
rename from Editor/UI/Presenters/CabinetPresenter.cs.meta
rename to Editor/UI/Presenters/AvatarPresenter.cs.meta
diff --git a/Editor/UI/Presenters/CabinetPresenter.cs b/Editor/UI/Presenters/CabinetPresenter.cs
deleted file mode 100644
index 78b56e72..00000000
--- a/Editor/UI/Presenters/CabinetPresenter.cs
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * File: CabinetPresenter.cs
- * Project: DressingTools
- * Created Date: Wednesday, August 9th 2023, 11:38:52 pm
- * Author: chocopoi (poi@chocopoi.com)
- * -----
- * Copyright (c) 2023 chocopoi
- *
- * This file is part of DressingTools.
- *
- * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
- *
- * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with DressingTools. If not, see .
- */
-
-using Chocopoi.DressingFramework.Localization;
-using Chocopoi.DressingTools.Components.OneConf;
-using Chocopoi.DressingTools.Localization;
-using Chocopoi.DressingTools.OneConf;
-using Chocopoi.DressingTools.OneConf.Cabinet;
-using Chocopoi.DressingTools.OneConf.Cabinet.Modules.BuiltIn;
-using Chocopoi.DressingTools.OneConf.Serialization;
-using Chocopoi.DressingTools.UI.Views;
-using UnityEditor;
-using UnityEngine;
-
-namespace Chocopoi.DressingTools.UI.Presenters
-{
- internal class CabinetPresenter
- {
- private static readonly I18nTranslator t = I18n.ToolTranslator;
-
- private ICabinetSubView _view;
- private CabinetConfig _cabinetConfig;
-
- public CabinetPresenter(ICabinetSubView view)
- {
- _view = view;
-
- SubscribeEvents();
- }
-
- private void SubscribeEvents()
- {
- _view.Load += OnLoad;
- _view.Unload += OnUnload;
-
- _view.AddWearableButtonClick += OnAddWearableButtonClick;
- _view.ForceUpdateView += OnForceUpdateView;
- _view.SelectedCabinetChange += OnSelectedCabinetChange;
- _view.CabinetSettingsChange += OnCabinetSettingsChange;
- _view.ToolbarCreateCabinetButtonClick += OnToolbarCreateCabinetButtonClick;
- _view.CreateCabinetStartButtonClick += OnCreateCabinetStartButtonClick;
- _view.CreateCabinetBackButtonClick += OnCreateCabinetBackButtonClick;
-
- EditorApplication.hierarchyChanged += OnHierarchyChanged;
- EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
- }
-
- private void UnsubscribeEvents()
- {
- _view.Load -= OnLoad;
- _view.Unload -= OnUnload;
-
- _view.AddWearableButtonClick -= OnAddWearableButtonClick;
- _view.ForceUpdateView -= OnForceUpdateView;
- _view.SelectedCabinetChange -= OnSelectedCabinetChange;
- _view.CabinetSettingsChange -= OnCabinetSettingsChange;
- _view.ToolbarCreateCabinetButtonClick -= OnToolbarCreateCabinetButtonClick;
- _view.CreateCabinetStartButtonClick -= OnCreateCabinetStartButtonClick;
- _view.CreateCabinetBackButtonClick -= OnCreateCabinetBackButtonClick;
-
- EditorApplication.hierarchyChanged -= OnHierarchyChanged;
- EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
- }
-
- private void OnPlayModeStateChanged(PlayModeStateChange change)
- {
- if (change == PlayModeStateChange.EnteredEditMode)
- {
- UpdateView();
- }
- }
-
- private void OnCreateCabinetBackButtonClick()
- {
- _view.ShowCreateCabinetPanel = false;
- UpdateView();
- }
-
- private void OnCreateCabinetStartButtonClick()
- {
- if (_view.CreateCabinetAvatarGameObject == null)
- {
- return;
- }
- OneConfUtils.GetAvatarCabinet(_view.CreateCabinetAvatarGameObject, true);
- _view.ShowCreateCabinetPanel = false;
- UpdateView();
- }
-
- private void OnToolbarCreateCabinetButtonClick()
- {
- _view.ShowCreateCabinetPanel = true;
- _view.ShowCreateCabinetBackButton = true;
- _view.CreateCabinetAvatarGameObject = null;
-
- UpdateView();
- }
-
- private void OnHierarchyChanged()
- {
- if (Application.isPlaying) return;
- UpdateView();
- }
-
-
- private void OnSelectedCabinetChange()
- {
- UpdateView();
- }
-
- private void OnCabinetSettingsChange()
- {
- var cabinets = OneConfUtils.GetAllCabinets();
-
- if (cabinets.Length == 0)
- {
- _view.ShowCreateCabinetBackButton = false;
- _view.ShowCreateCabinetPanel = true;
- return;
- }
- _view.ShowCreateCabinetBackButton = true;
-
- var cabinet = cabinets[_view.SelectedCabinetIndex];
-
- cabinet.RootGameObject = _view.CabinetAvatarGameObject;
-
- if (_cabinetConfig == null)
- {
- Debug.LogWarning("[DressingTools] Cabinet config is uninitialized from UI but cabinet settings changed.");
- return;
- }
-
- _cabinetConfig.avatarArmatureName = _view.CabinetAvatarArmatureName;
- _cabinetConfig.groupDynamics = _view.CabinetGroupDynamics;
- _cabinetConfig.groupDynamicsSeparateGameObjects = _view.CabinetGroupDynamicsSeparateGameObjects;
- _cabinetConfig.animationWriteDefaultsMode = (CabinetConfig.WriteDefaultsMode)_view.CabinetAnimationWriteDefaultsMode;
-
- var cabAnimConfig = _cabinetConfig.FindModuleConfig();
- if (cabAnimConfig == null)
- {
- cabAnimConfig = new CabinetAnimCabinetModuleConfig();
- _cabinetConfig.modules.Add(new CabinetModule()
- {
- config = cabAnimConfig,
- moduleName = CabinetAnimCabinetModuleConfig.ModuleIdentifier
- });
- }
-
- cabAnimConfig.thumbnails = _view.CabinetUseThumbnailsAsMenuIcons;
- cabAnimConfig.menuInstallPath = _view.CabinetMenuInstallPath;
- cabAnimConfig.menuItemName = _view.CabinetMenuItemName;
- cabAnimConfig.networkSynced = _view.CabinetNetworkSynced;
- cabAnimConfig.saved = _view.CabinetSaved;
- cabAnimConfig.resetCustomizablesOnSwitch = _view.CabinetResetCustomizablesOnSwitch;
-
- cabinet.ConfigJson = CabinetConfigUtility.Serialize(_cabinetConfig);
- }
-
- private void OnForceUpdateView()
- {
- UpdateView();
- }
-
- public void SelectCabinet(DTCabinet cabinet)
- {
- var cabinets = OneConfUtils.GetAllCabinets();
-
- if (cabinets.Length == 0)
- {
- _view.ShowCreateCabinetBackButton = false;
- _view.ShowCreateCabinetPanel = true;
- return;
- }
- _view.ShowCreateCabinetBackButton = true;
- _view.ShowCreateCabinetPanel = false;
-
- // refresh the keys first
- UpdateCabinetSelectionDropdown(cabinets);
-
- // find matching index
- for (var i = 0; i < cabinets.Length; i++)
- {
- if (cabinets[i] == cabinet)
- {
- _view.SelectedCabinetIndex = i;
- break;
- }
- }
-
- // update
- UpdateView();
- }
-
- private void UpdateCabinetSelectionDropdown(DTCabinet[] cabinets)
- {
- // cabinet selection dropdown
- _view.AvailableCabinetSelections.Clear();
- for (var i = 0; i < cabinets.Length; i++)
- {
- _view.AvailableCabinetSelections.Add(cabinets[i].RootGameObject != null ? cabinets[i].RootGameObject.name : t._("cabinet.editor.cabinetContent.popup.cabinetOptions.cabinetNameNoGameObjectAttached", i + 1));
- }
- }
-
- private void UpdateCabinetContentView()
- {
- var cabinets = OneConfUtils.GetAllCabinets();
-
- if (cabinets.Length == 0)
- {
- _view.ShowCreateCabinetBackButton = false;
- _view.ShowCreateCabinetPanel = true;
- return;
- }
- _view.ShowCreateCabinetBackButton = true;
-
- UpdateCabinetSelectionDropdown(cabinets);
-
- if (_view.SelectedCabinetIndex < 0 || _view.SelectedCabinetIndex >= cabinets.Length)
- {
- // invalid selected cabinet index, setting it back to 0
- _view.SelectedCabinetIndex = 0;
- }
-
- // clear views
- _view.InstalledWearablePreviews.Clear();
-
- // update selected cabinet view
- var cabinet = cabinets[_view.SelectedCabinetIndex];
-
- // cabinet json is broken, ask user whether to make a new one or not
- if (!CabinetConfigUtility.TryDeserialize(cabinet.ConfigJson, out _cabinetConfig) || !_cabinetConfig.IsValid())
- {
- Debug.LogWarning("[DressingTools] [CabinetPresenter] Unable to deserialize cabinet config or invalid configuration! Using new config instead");
- _cabinetConfig = new CabinetConfig();
- cabinet.ConfigJson = CabinetConfigUtility.Serialize(_cabinetConfig);
- }
-
- _view.CabinetAvatarGameObject = cabinet.RootGameObject;
- _view.CabinetAvatarArmatureName = _cabinetConfig.avatarArmatureName;
- _view.CabinetGroupDynamics = _cabinetConfig.groupDynamics;
- _view.CabinetGroupDynamicsSeparateGameObjects = _cabinetConfig.groupDynamicsSeparateGameObjects;
- _view.CabinetAnimationWriteDefaultsMode = (int)_cabinetConfig.animationWriteDefaultsMode;
- UpdateCabinetAnimationConfig();
-
- var wearables = OneConfUtils.GetCabinetWearables(cabinet.RootGameObject);
-
- foreach (var wearable in wearables)
- {
- var config = WearableConfigUtility.Deserialize(wearable.ConfigJson);
- _view.InstalledWearablePreviews.Add(new WearablePreview()
- {
- name = config != null ?
- config.info.name :
- t._("cabinet.editor.cabinetContent.wearablePreview.name.unableToLoadConfiguration"),
- thumbnail = config != null && config.info.thumbnail != null ?
- OneConfUtils.GetTextureFromBase64(config.info.thumbnail) :
- null,
- RemoveButtonClick = () =>
- {
- if (wearable is DTWearable dtWearable)
- {
- cabinet.RemoveWearable(dtWearable);
- UpdateView();
- }
- else
- {
- Debug.LogWarning("[DressingTools] Removing non-DressingTools wearable is not currently supported");
- }
- },
- EditButtonClick = () =>
- {
- _view.StartDressing(cabinet.RootGameObject, wearable.RootGameObject);
- }
- });
- }
- }
-
- private void UpdateCabinetAnimationConfig()
- {
- var cabAnimConfig = _cabinetConfig.FindModuleConfig() ?? new CabinetAnimCabinetModuleConfig();
- _view.CabinetUseThumbnailsAsMenuIcons = cabAnimConfig.thumbnails;
- _view.CabinetMenuInstallPath = cabAnimConfig.menuInstallPath;
- _view.CabinetMenuItemName = cabAnimConfig.menuItemName;
- _view.CabinetNetworkSynced = cabAnimConfig.networkSynced;
- _view.CabinetSaved = cabAnimConfig.saved;
- _view.CabinetResetCustomizablesOnSwitch = cabAnimConfig.resetCustomizablesOnSwitch;
- }
-
- private void UpdateView()
- {
- UpdateCabinetContentView();
- _view.Repaint();
- }
-
- private void OnLoad()
- {
- UpdateView();
- }
-
- private void OnUnload()
- {
- UnsubscribeEvents();
- }
-
- private void OnAddWearableButtonClick()
- {
- var cabinets = OneConfUtils.GetAllCabinets();
-
- if (cabinets.Length == 0 || _view.SelectedCabinetIndex < 0 || _view.SelectedCabinetIndex >= cabinets.Length)
- {
- return;
- }
-
- var cabinet = cabinets[_view.SelectedCabinetIndex];
- _view.StartDressing(cabinet.RootGameObject);
- }
- }
-}
diff --git a/Editor/UI/Presenters/MainPresenter.cs b/Editor/UI/Presenters/MainPresenter.cs
index 8efa263e..84f04987 100644
--- a/Editor/UI/Presenters/MainPresenter.cs
+++ b/Editor/UI/Presenters/MainPresenter.cs
@@ -15,9 +15,14 @@
* You should have received a copy of the GNU General Public License along with DressingTools. If not, see .
*/
+using System;
+using System.Collections.Generic;
using Chocopoi.DressingFramework.Localization;
+using Chocopoi.DressingTools.Configurator.Avatar;
using Chocopoi.DressingTools.UI.Views;
using UnityEditor;
+using UnityEngine;
+using UnityEngine.SceneManagement;
#if UNITY_2021_2_OR_NEWER
using PrefabStage = UnityEditor.SceneManagement.PrefabStage;
@@ -53,9 +58,13 @@ private void SubscribeEvents()
_view.MouseMove += OnMouseMove;
_view.UpdateAvailableUpdateButtonClick += OnUpdateAvailableUpdateButtonClick;
- EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
_view.PrefabStageOpened += OnPrefabStageOpened;
_view.PrefabStageClosing += OnPrefabStageClosing;
+ _view.AvatarSelectionPopupChange += OnSelectedAvatarChange;
+
+ EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
+ EditorApplication.hierarchyChanged += OnHierarchyChanged;
+ Selection.selectionChanged += OnGameObjectSelectionChanged;
}
private void UnsubscribeEvents()
@@ -65,9 +74,68 @@ private void UnsubscribeEvents()
_view.MouseMove -= OnMouseMove;
_view.UpdateAvailableUpdateButtonClick -= OnUpdateAvailableUpdateButtonClick;
+ _view.PrefabStageOpened -= OnPrefabStageOpened;
+ _view.PrefabStageClosing -= OnPrefabStageClosing;
+ _view.AvatarSelectionPopupChange -= OnSelectedAvatarChange;
+
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
- PrefabStage.prefabStageOpened -= OnPrefabStageOpened;
- PrefabStage.prefabStageClosing -= OnPrefabStageClosing;
+ EditorApplication.hierarchyChanged -= OnHierarchyChanged;
+ Selection.selectionChanged -= OnGameObjectSelectionChanged;
+ }
+
+ private void OnHierarchyChanged()
+ {
+ if (Application.isPlaying) return;
+ UpdateView();
+ }
+
+ private void OnGameObjectSelectionChanged()
+ {
+ if (Selection.activeGameObject == null) return;
+
+ var avatar = AvatarUtils.GetAvatarRoot(Selection.activeGameObject);
+ if (avatar != null)
+ {
+ SelectAvatar(avatar);
+ }
+ }
+
+ private void OnSelectedAvatarChange()
+ {
+ UpdateView();
+ }
+
+ public void SelectAvatar(GameObject avatarGameObject)
+ {
+ // refresh the keys first
+ _view.AvailableAvatars = AvatarUtils.FindSceneAvatars(SceneManager.GetActiveScene());
+
+ // find matching index
+ for (var i = 0; i < _view.AvailableAvatars.Count; i++)
+ {
+ if (_view.AvailableAvatars[i] == avatarGameObject)
+ {
+ _view.SelectedAvatarIndex = i;
+ break;
+ }
+ }
+
+ // update
+ UpdateView();
+ }
+
+ private void UpdateView()
+ {
+ _view.AvailableAvatars = AvatarUtils.FindSceneAvatars(SceneManager.GetActiveScene());
+ if (_view.SelectedAvatarIndex < 0 || _view.SelectedAvatarIndex >= _view.AvailableAvatars.Count)
+ {
+ // invalid selected cabinet index, setting it back to 0
+ _view.SelectedAvatarIndex = 0;
+ }
+
+ _view.ToolVersionText = UpdateChecker.CurrentVersion?.fullString;
+
+ _view.Repaint();
}
private void OnPlayModeStateChanged(PlayModeStateChange change)
@@ -123,6 +191,7 @@ private void OnPrefabStageClosing(PrefabStage stage)
private void OnLoad()
{
+ UpdateView();
}
private void OnUnload()
diff --git a/Editor/UI/Presenters/DressingPresenter.cs b/Editor/UI/Presenters/OneConfDressPresenter.cs
similarity index 97%
rename from Editor/UI/Presenters/DressingPresenter.cs
rename to Editor/UI/Presenters/OneConfDressPresenter.cs
index 76272ec8..043fff21 100644
--- a/Editor/UI/Presenters/DressingPresenter.cs
+++ b/Editor/UI/Presenters/OneConfDressPresenter.cs
@@ -24,11 +24,11 @@
namespace Chocopoi.DressingTools.UI.Presenters
{
- internal class DressingPresenter
+ internal class OneConfDressPresenter
{
- private IDressingSubView _view;
+ private IOneConfDressSubView _view;
- public DressingPresenter(IDressingSubView view)
+ public OneConfDressPresenter(IOneConfDressSubView view)
{
_view = view;
diff --git a/Editor/UI/Presenters/DressingPresenter.cs.meta b/Editor/UI/Presenters/OneConfDressPresenter.cs.meta
similarity index 100%
rename from Editor/UI/Presenters/DressingPresenter.cs.meta
rename to Editor/UI/Presenters/OneConfDressPresenter.cs.meta
diff --git a/Editor/UI/Presenters/WearableConfigPresenter.cs b/Editor/UI/Presenters/OneConfWearableConfigPresenter.cs
similarity index 99%
rename from Editor/UI/Presenters/WearableConfigPresenter.cs
rename to Editor/UI/Presenters/OneConfWearableConfigPresenter.cs
index bc2a3fb9..8578d091 100644
--- a/Editor/UI/Presenters/WearableConfigPresenter.cs
+++ b/Editor/UI/Presenters/OneConfWearableConfigPresenter.cs
@@ -36,7 +36,7 @@
namespace Chocopoi.DressingTools.UI.Presenters
{
- internal class WearableConfigPresenter
+ internal class OneConfWearableConfigPresenter
{
private static readonly I18nTranslator t = I18n.ToolTranslator;
private const string OldDresserClassName = "Chocopoi.DressingTools.Dresser.Standard.DefaultDresser";
@@ -44,9 +44,9 @@ internal class WearableConfigPresenter
private List s_moduleProviders = null;
private static Dictionary s_moduleEditorTypesCache = null;
- private IWearableConfigView _view;
+ private IOneConfWearableConfigView _view;
- public WearableConfigPresenter(IWearableConfigView view)
+ public OneConfWearableConfigPresenter(IOneConfWearableConfigView view)
{
_view = view;
SubscribeEvents();
diff --git a/Editor/UI/Presenters/WearableConfigPresenter.cs.meta b/Editor/UI/Presenters/OneConfWearableConfigPresenter.cs.meta
similarity index 100%
rename from Editor/UI/Presenters/WearableConfigPresenter.cs.meta
rename to Editor/UI/Presenters/OneConfWearableConfigPresenter.cs.meta
diff --git a/Editor/UI/Presenters/SettingsPresenter.cs b/Editor/UI/Presenters/ToolSettingsPresenter.cs
similarity index 68%
rename from Editor/UI/Presenters/SettingsPresenter.cs
rename to Editor/UI/Presenters/ToolSettingsPresenter.cs
index b21c0ef8..e482cf24 100644
--- a/Editor/UI/Presenters/SettingsPresenter.cs
+++ b/Editor/UI/Presenters/ToolSettingsPresenter.cs
@@ -24,26 +24,17 @@
namespace Chocopoi.DressingTools.UI.Presenters
{
- internal class SettingsPresenter
+ internal class ToolSettingsPresenter
{
private static readonly I18nTranslator t = I18n.ToolTranslator;
- private ISettingsSubView _view;
+ private IToolSettingsSubView _view;
private Preferences _prefs;
- private string[] _availableLocales;
- public SettingsPresenter(ISettingsSubView view)
+ public ToolSettingsPresenter(IToolSettingsSubView view)
{
_view = view;
_prefs = PreferencesUtility.GetPreferences();
-
- _availableLocales = t.GetAvailableLocales();
- _view.AvailableLanguageKeys.Clear();
- foreach (var locale in _availableLocales)
- {
- _view.AvailableLanguageKeys.Add(new CultureInfo(locale).NativeName);
- }
-
SubscribeEvents();
}
@@ -53,7 +44,6 @@ private void SubscribeEvents()
_view.Unload += OnUnload;
_view.ForceUpdateView += OnForceUpdateView;
- _view.LanguageChanged += OnLanguageChanged;
_view.UpdaterCheckUpdateButtonClicked += OnUpdaterCheckUpdateButtonClicked;
_view.ResetToDefaultsButtonClicked += OnResetToDefaultsButtonClicked;
}
@@ -64,7 +54,6 @@ private void UnsubscribeEvents()
_view.Unload -= OnUnload;
_view.ForceUpdateView -= OnForceUpdateView;
- _view.LanguageChanged -= OnLanguageChanged;
_view.UpdaterCheckUpdateButtonClicked -= OnUpdaterCheckUpdateButtonClicked;
_view.ResetToDefaultsButtonClicked -= OnResetToDefaultsButtonClicked;
}
@@ -87,29 +76,6 @@ private void OnUpdaterCheckUpdateButtonClicked()
UpdateView();
}
- private void OnLanguageChanged()
- {
- var localeIndex = _view.AvailableLanguageKeys.IndexOf(_view.LanguageSelected);
- var locale = _availableLocales[localeIndex];
-
- _prefs.app.selectedLanguage = locale;
- I18nManager.Instance.SetLocale(locale);
- PreferencesUtility.SavePreferences();
-
- _view.ShowLanguageReloadWindowHelpbox = true;
- _view.Repaint();
- }
-
- private void UpdateLanguagePopupView()
- {
- var localeIndex = Array.IndexOf(_availableLocales, _prefs.app.selectedLanguage);
- if (localeIndex == -1)
- {
- localeIndex = 0;
- }
- _view.LanguageSelected = _view.AvailableLanguageKeys[localeIndex];
- }
-
private void UpdateUpdateCheckerView()
{
_view.UpdaterCurrentVersion = UpdateChecker.CurrentVersion?.fullString;
@@ -118,7 +84,6 @@ private void UpdateUpdateCheckerView()
private void UpdateView()
{
- UpdateLanguagePopupView();
UpdateUpdateCheckerView();
_view.Repaint();
diff --git a/Editor/UI/Presenters/SettingsPresenter.cs.meta b/Editor/UI/Presenters/ToolSettingsPresenter.cs.meta
similarity index 100%
rename from Editor/UI/Presenters/SettingsPresenter.cs.meta
rename to Editor/UI/Presenters/ToolSettingsPresenter.cs.meta
diff --git a/Editor/UI/Views/AvatarSubView.cs b/Editor/UI/Views/AvatarSubView.cs
new file mode 100644
index 00000000..e6014bdf
--- /dev/null
+++ b/Editor/UI/Views/AvatarSubView.cs
@@ -0,0 +1,233 @@
+/*
+ * File: CabinetSubView.cs
+ * Project: DressingTools
+ * Created Date: Wednesday, August 9th 2023, 11:38:52 pm
+ * Author: chocopoi (poi@chocopoi.com)
+ * -----
+ * Copyright (c) 2023 chocopoi
+ *
+ * This file is part of DressingTools.
+ *
+ * DressingTools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * DressingTools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with DressingTools. If not, see .
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using Chocopoi.DressingFramework.Localization;
+using Chocopoi.DressingTools.Configurator.Avatar;
+using Chocopoi.DressingTools.Localization;
+using Chocopoi.DressingTools.UI.Presenters;
+using UnityEditor;
+using UnityEditor.UIElements;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Chocopoi.DressingTools.UI.Views
+{
+ [ExcludeFromCodeCoverage]
+ internal class AvatarSubView : ElementView, IAvatarSubView
+ {
+ private static readonly I18nTranslator t = I18n.ToolTranslator;
+ private static VisualTreeAsset s_avatarOutfitPreviewVisualTree = null;
+ private static VisualTreeAsset s_addWearablePlaceholderVisualTree = null;
+ private static StyleSheet s_avatarSubViewStyleSheet = null;
+ private static Texture2D s_thumbnailPlaceholder = null;
+
+ public event Action AddOutfitButtonClick;
+ public event Action AvatarSettingsChange;
+
+ public int SettingsAnimationWriteDefaultsMode { get; set; }
+ public List InstalledOutfitPreviews { get; set; }
+ public GameObject SelectedAvatarGameObject { get => _mainView.SelectedAvatarGameObject; }
+
+ private IMainView _mainView;
+ private AvatarPresenter _avatarPresenter;
+ private VisualElement _avatarContentContainer;
+ private VisualElement _wardrobeSettingsContainer;
+ private VisualElement _installedOutfitContainer;
+ private PopupField _animationWriteDefaultsPopup;
+
+ public AvatarSubView(IMainView mainView)
+ {
+ _mainView = mainView;
+ _avatarPresenter = new AvatarPresenter(this);
+
+ InstalledOutfitPreviews = new List();
+
+ SettingsAnimationWriteDefaultsMode = 0;
+ }
+
+ public void SelectTab(int selectedTab)
+ {
+ _mainView.SelectedTab = selectedTab;
+ }
+
+ public void StartDressing(GameObject outfitGameObject = null, GameObject avatarGameObject = null)
+ {
+ _mainView.StartDressing(outfitGameObject, avatarGameObject);
+ }
+
+ private void InitSettings()
+ {
+ // TODO: Create view from avatar settings directly?
+ var container = Q("settings-container").First();
+ container.Clear();
+
+ container.Add(CreateHelpBox(t._("editor.main.avatar.settings.avatar.helpbox.conceptAndLocationChanged"), MessageType.Info));
+
+ var choices = new List() {
+ t._("editor.main.avatar.settings.avatar.popup.animationWriteDefaultsMode.auto"),
+ t._("editor.main.avatar.settings.avatar.popup.animationWriteDefaultsMode.on"),
+ t._("editor.main.avatar.settings.avatar.popup.animationWriteDefaultsMode.off")
+ };
+ _animationWriteDefaultsPopup = new PopupField(t._("editor.main.avatar.settings.avatar.popup.animationWriteDefaultsMode"), choices, 0);
+ _animationWriteDefaultsPopup.RegisterValueChangedCallback((evt) =>
+ {
+ SettingsAnimationWriteDefaultsMode = _animationWriteDefaultsPopup.index;
+ AvatarSettingsChange?.Invoke();
+ });
+ container.Add(_animationWriteDefaultsPopup);
+
+ _wardrobeSettingsContainer = new VisualElement();
+ container.Add(_wardrobeSettingsContainer);
+ }
+
+ private void BindFoldouts()
+ {
+ BindFoldoutHeaderWithContainer("foldout-settings", "settings-container");
+ BindFoldoutHeaderWithContainer("foldout-outfits", "outfits-container");
+ }
+
+ private void InitAvatarContent()
+ {
+ _avatarContentContainer = Q("cabinet-content-container").First();
+ _installedOutfitContainer = Q("outfits-container").First();
+
+ InitSettings();
+ BindFoldouts();
+ }
+
+ private void InitVisualTree()
+ {
+ var tree = Resources.Load("AvatarSubView");
+ tree.CloneTree(this);
+ // var styleSheet = Resources.Load("AvatarSubViewStyles");
+ // if (!styleSheets.Contains(styleSheet))
+ // {
+ // styleSheets.Add(styleSheet);
+ // }
+ }
+
+ public override void OnEnable()
+ {
+ InitVisualTree();
+ InitAvatarContent();
+
+ t.LocalizeElement(this);
+
+ RaiseLoadEvent();
+ }
+
+ private VisualElement CreateAddPlaceholderElement()
+ {
+ if (s_addWearablePlaceholderVisualTree == null)
+ {
+ s_addWearablePlaceholderVisualTree = Resources.Load("AddOutfitPlaceholder");
+ }
+
+ if (s_avatarSubViewStyleSheet == null)
+ {
+ s_avatarSubViewStyleSheet = Resources.Load("AvatarSubViewStyles");
+ }
+
+ var element = new VisualElement();
+ element.style.width = 128;
+ element.style.height = 128;
+ element.styleSheets.Add(s_avatarSubViewStyleSheet);
+ s_addWearablePlaceholderVisualTree.CloneTree(element);
+ t.LocalizeElement(element);
+
+ element.RegisterCallback((MouseDownEvent evt) => AddOutfitButtonClick?.Invoke());
+ element.RegisterCallback((MouseEnterEvent evt) => element.EnableInClassList("hover", true));
+ element.RegisterCallback((MouseLeaveEvent evt) => element.EnableInClassList("hover", false));
+
+ return element;
+ }
+
+ private VisualElement CreateAvatarOutfitPreviewElement(string wearableName, Texture2D thumbnail, Action removeBtnClick, Action editBtnClick)
+ {
+ if (s_avatarOutfitPreviewVisualTree == null)
+ {
+ s_avatarOutfitPreviewVisualTree = Resources.Load("AvatarOutfitPreview");
+ }
+
+ if (s_avatarSubViewStyleSheet == null)
+ {
+ s_avatarSubViewStyleSheet = Resources.Load("AvatarSubViewStyles");
+ }
+
+ if (s_thumbnailPlaceholder == null)
+ {
+ s_thumbnailPlaceholder = Resources.Load("thumbnailPlaceholder");
+ }
+
+ var element = new VisualElement();
+ element.style.width = 128;
+ element.style.height = 128;
+ element.styleSheets.Add(s_avatarSubViewStyleSheet);
+ s_avatarOutfitPreviewVisualTree.CloneTree(element);
+ t.LocalizeElement(element);
+
+ element.Q