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