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..3ed11957 --- /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 null; + } + + 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 null; + } + } +} diff --git a/Editor/UI/Views/WearableConfigView.cs.meta b/Editor/Configurator/Avatar/AvatarUtils.cs.meta similarity index 83% rename from Editor/UI/Views/WearableConfigView.cs.meta rename to Editor/Configurator/Avatar/AvatarUtils.cs.meta index 62fdbfe3..50c651c5 100644 --- a/Editor/UI/Views/WearableConfigView.cs.meta +++ b/Editor/Configurator/Avatar/AvatarUtils.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b2ad4dd1add6d4e4993ac574131c425c +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/Tests~/Editor/UI/Presenters/DressingPresenterTest.cs.meta b/Editor/Configurator/Avatar/IAvatarSettings.cs.meta similarity index 83% rename from Tests~/Editor/UI/Presenters/DressingPresenterTest.cs.meta rename to Editor/Configurator/Avatar/IAvatarSettings.cs.meta index 3dce1765..a7e34d00 100644 --- a/Tests~/Editor/UI/Presenters/DressingPresenterTest.cs.meta +++ b/Editor/Configurator/Avatar/IAvatarSettings.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: f2e152828377d2c499af2ca00f7fdbb7 +guid: 190df016052766240b40cfd53fd01fe0 MonoImporter: externalObjects: {} serializedVersion: 2 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/DTBaseOutfitProvider.cs b/Editor/Configurator/Cabinet/DTBaseOutfitProvider.cs new file mode 100644 index 00000000..6658e315 --- /dev/null +++ b/Editor/Configurator/Cabinet/DTBaseOutfitProvider.cs @@ -0,0 +1,20 @@ +/* + * 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.Cabinet +{ + internal class DTBaseOutfitProvider : IBaseOutfitProvider + { + } +} diff --git a/Editor/Configurator/Cabinet/DTBaseOutfitProvider.cs.meta b/Editor/Configurator/Cabinet/DTBaseOutfitProvider.cs.meta new file mode 100644 index 00000000..c3373fce --- /dev/null +++ b/Editor/Configurator/Cabinet/DTBaseOutfitProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2d742936c41466a459a80d7d12f34071 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Cabinet/DTConfigurableOutfit.cs b/Editor/Configurator/Cabinet/DTConfigurableOutfit.cs new file mode 100644 index 00000000..b3b1a4d8 --- /dev/null +++ b/Editor/Configurator/Cabinet/DTConfigurableOutfit.cs @@ -0,0 +1,50 @@ +/* + * 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.Components.Cabinet; +using Chocopoi.DressingTools.Configurator.Modules; +using Chocopoi.DressingTools.UI.Views; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Chocopoi.DressingTools.Configurator.Cabinet +{ + internal class DTConfigurableOutfit : IConfigurableOutfit + { + public Transform RootTransform { get => _outfitComp.RootTransform; } + public string Name => _outfitComp.Name; + public Texture2D Icon => _outfitComp.Icon; + + private readonly DTAlternateOutfit _outfitComp; + + public DTConfigurableOutfit(DTAlternateOutfit outfitComp) + { + _outfitComp = outfitComp; + } + + public VisualElement CreateView() + { + throw new System.NotImplementedException(); + } + + public List GetModules() + { + throw new System.NotImplementedException(); + } + + public void Preview(GameObject previewAvatarGameObject, GameObject previewOutfitGameObject) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Editor/Configurator/Cabinet/DTConfigurableOutfit.cs.meta b/Editor/Configurator/Cabinet/DTConfigurableOutfit.cs.meta new file mode 100644 index 00000000..df17adc0 --- /dev/null +++ b/Editor/Configurator/Cabinet/DTConfigurableOutfit.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e3d09a3cbb4ff1142a5df53ee3195a81 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Cabinet/DTWardrobeProvider.cs b/Editor/Configurator/Cabinet/DTWardrobeProvider.cs new file mode 100644 index 00000000..05da2186 --- /dev/null +++ b/Editor/Configurator/Cabinet/DTWardrobeProvider.cs @@ -0,0 +1,50 @@ +/* + * 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.Components.Cabinet; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Chocopoi.DressingTools.Configurator.Cabinet +{ + internal class DTWardrobeProvider : IWardrobeProvider + { + private readonly GameObject _avatarGameObject; + + public DTWardrobeProvider(GameObject avatarGameObject) + { + _avatarGameObject = avatarGameObject; + } + + public VisualElement CreateView() + { + throw new System.NotImplementedException(); + } + + public List GetOutfits() + { + var comps = _avatarGameObject.GetComponentsInChildren(true); + var outfits = new List(); + foreach (var comp in comps) + { + outfits.Add(new DTConfigurableOutfit(comp)); + } + return outfits; + } + + public void RemoveOutfit(IConfigurableOutfit outfit) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Editor/Configurator/Cabinet/DTWardrobeProvider.cs.meta b/Editor/Configurator/Cabinet/DTWardrobeProvider.cs.meta new file mode 100644 index 00000000..28792871 --- /dev/null +++ b/Editor/Configurator/Cabinet/DTWardrobeProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0798e99e32176664d95f987ff0ce47d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Cabinet/IBaseOutfitProvider.cs b/Editor/Configurator/Cabinet/IBaseOutfitProvider.cs new file mode 100644 index 00000000..f9c3df13 --- /dev/null +++ b/Editor/Configurator/Cabinet/IBaseOutfitProvider.cs @@ -0,0 +1,20 @@ +/* + * 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.Cabinet +{ + internal interface IBaseOutfitProvider + { + } +} diff --git a/Editor/Configurator/Cabinet/IBaseOutfitProvider.cs.meta b/Editor/Configurator/Cabinet/IBaseOutfitProvider.cs.meta new file mode 100644 index 00000000..cf49bac4 --- /dev/null +++ b/Editor/Configurator/Cabinet/IBaseOutfitProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c74cc891dc33f3a4abaa1a95de76e3ad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Cabinet/IConfigurableOutfit.cs b/Editor/Configurator/Cabinet/IConfigurableOutfit.cs new file mode 100644 index 00000000..32c6f64d --- /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..6fed89c2 --- /dev/null +++ b/Editor/Configurator/Cabinet/OneConfConfigurableOutfit.cs @@ -0,0 +1,146 @@ +/* + * 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 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 Chocopoi.DressingTools.OneConf.Wearable.Modules.BuiltIn; +using Chocopoi.DressingTools.UI.Views; +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/Configurator/Modules.meta b/Editor/Configurator/Modules.meta new file mode 100644 index 00000000..59af3ea1 --- /dev/null +++ b/Editor/Configurator/Modules.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3785e4e8e0f78a748a10bb3f47b8d93a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Modules/ArmatureMerging.meta b/Editor/Configurator/Modules/ArmatureMerging.meta new file mode 100644 index 00000000..8b6ac717 --- /dev/null +++ b/Editor/Configurator/Modules/ArmatureMerging.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e65aac42077722345b341ce308eed3e7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Modules/ArmatureMerging/DTArmatureMappingModule.cs b/Editor/Configurator/Modules/ArmatureMerging/DTArmatureMappingModule.cs new file mode 100644 index 00000000..0e533edf --- /dev/null +++ b/Editor/Configurator/Modules/ArmatureMerging/DTArmatureMappingModule.cs @@ -0,0 +1,49 @@ +/* + * 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 Chocopoi.AvatarLib.Animations; +using Chocopoi.DressingTools.Components.Modifiers; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Chocopoi.DressingTools.Configurator.Modules +{ + internal class DTArmatureMappingModule : IArmatureMergingModule + { + public Transform TargetArmature + { + get => string.IsNullOrEmpty(_comp.TargetArmaturePath) ? + null : + _avatarGameObject.transform.Find(_comp.TargetArmaturePath); + set => + _comp.TargetArmaturePath = + (value == null || value == _avatarGameObject.transform) ? + "" : + AnimationUtils.GetRelativePath(value.transform, _avatarGameObject.transform); + } + public Transform SourceArmature { get => _comp.SourceArmature; set => _comp.SourceArmature = value; } + + private readonly GameObject _avatarGameObject; + private readonly DTArmatureMapping _comp; + + public DTArmatureMappingModule(GameObject avatarGameObject, DTArmatureMapping comp) + { + _avatarGameObject = avatarGameObject; + _comp = comp; + } + + public VisualElement CreateView() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Editor/Configurator/Modules/ArmatureMerging/DTArmatureMappingModule.cs.meta b/Editor/Configurator/Modules/ArmatureMerging/DTArmatureMappingModule.cs.meta new file mode 100644 index 00000000..b50ee4be --- /dev/null +++ b/Editor/Configurator/Modules/ArmatureMerging/DTArmatureMappingModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bdafac00c0d9bda4a9f3213fc7450291 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Modules/ArmatureMerging/IArmatureMergingModule.cs b/Editor/Configurator/Modules/ArmatureMerging/IArmatureMergingModule.cs new file mode 100644 index 00000000..0e084a3f --- /dev/null +++ b/Editor/Configurator/Modules/ArmatureMerging/IArmatureMergingModule.cs @@ -0,0 +1,22 @@ +/* + * 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.Modules +{ + internal interface IArmatureMergingModule : IModule + { + Transform TargetArmature { get; set; } + Transform SourceArmature { get; set; } + } +} diff --git a/Editor/Configurator/Modules/ArmatureMerging/IArmatureMergingModule.cs.meta b/Editor/Configurator/Modules/ArmatureMerging/IArmatureMergingModule.cs.meta new file mode 100644 index 00000000..7737f443 --- /dev/null +++ b/Editor/Configurator/Modules/ArmatureMerging/IArmatureMergingModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ed350f2e48837b74aa3c72a2d62630cd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Modules/ArmatureMerging/MAMergeArmatureModule.cs b/Editor/Configurator/Modules/ArmatureMerging/MAMergeArmatureModule.cs new file mode 100644 index 00000000..374c8ddb --- /dev/null +++ b/Editor/Configurator/Modules/ArmatureMerging/MAMergeArmatureModule.cs @@ -0,0 +1,70 @@ +/* + * 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 . + */ +#if DT_MA +using Chocopoi.DressingFramework; +using nadena.dev.modular_avatar.core; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Chocopoi.DressingTools.Configurator.Modules +{ + internal class MAMergeArmatureModule : IArmatureMergingModule + { + public Transform TargetArmature + { + get => _comp.mergeTargetObject != null ? _comp.mergeTargetObject.transform : null; + set => _comp.mergeTarget.Set(value != null ? value.gameObject : null); + } + public Transform SourceArmature + { + get => _comp.transform; + set => MoveToNewSourceArmature(value); + } + + private ModularAvatarMergeArmature _comp; + + public MAMergeArmatureModule(ModularAvatarMergeArmature comp) + { + _comp = comp; + } + + private void MoveToNewSourceArmature(Transform sourceArmature) + { + if (sourceArmature == null) + { + return; + } + + if (sourceArmature.gameObject.TryGetComponent(out var existingComp)) + { + Debug.LogWarning($"Existing ModularAvatarMergeArmature component detected at {sourceArmature.name}, removing"); + Object.DestroyImmediate(existingComp); + } + + var comp = DKEditorUtils.CopyComponent(_comp, sourceArmature.gameObject); + if (comp == null || !(comp is ModularAvatarMergeArmature newComp)) + { + Debug.LogError("Failed to clone ModularAvatarMergeArmature component"); + return; + } + + Object.DestroyImmediate(_comp); + _comp = newComp; + } + + public VisualElement CreateView() + { + throw new System.NotImplementedException(); + } + } +} +#endif diff --git a/Editor/Configurator/Modules/ArmatureMerging/MAMergeArmatureModule.cs.meta b/Editor/Configurator/Modules/ArmatureMerging/MAMergeArmatureModule.cs.meta new file mode 100644 index 00000000..2fee3b9a --- /dev/null +++ b/Editor/Configurator/Modules/ArmatureMerging/MAMergeArmatureModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a289e3dea4e3ad54ab55d6d2b2d07f66 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Modules/ArmatureMerging/OneConfArmatureMappingModule.cs b/Editor/Configurator/Modules/ArmatureMerging/OneConfArmatureMappingModule.cs new file mode 100644 index 00000000..d76a35a1 --- /dev/null +++ b/Editor/Configurator/Modules/ArmatureMerging/OneConfArmatureMappingModule.cs @@ -0,0 +1,78 @@ +/* + * 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.AvatarLib.Animations; +using Chocopoi.DressingTools.Components.OneConf; +using Chocopoi.DressingTools.OneConf; +using Chocopoi.DressingTools.OneConf.Wearable.Modules.BuiltIn; +using Chocopoi.DressingTools.UI.Views.Modules; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Chocopoi.DressingTools.Configurator.Modules +{ + internal class OneConfArmatureMappingModule : OneConfModuleBase, IArmatureMergingModule + { + public Transform TargetArmature + { + get + { + ReadCabinetConfig(out _, out var config); + return string.IsNullOrEmpty(config.avatarArmatureName) ? + null : + OneConfUtils.GuessArmature(_avatarGameObject, config.avatarArmatureName); + } + set + { + WriteCabinetConfig((comp, config) => + { + config.avatarArmatureName = + value == null ? + "" : + AnimationUtils.GetRelativePath(value.transform, comp.transform); + }); + } + } + public Transform SourceArmature + { + get + { + ReadWearableModule(out var module); + if (module == null) return null; + return string.IsNullOrEmpty(module.wearableArmatureName) ? + null : + OneConfUtils.GuessArmature(_wearableComp.gameObject, module.wearableArmatureName); + } + set + { + WriteWearableModule(module => + { + module.wearableArmatureName = + value == null ? + "" : + AnimationUtils.GetRelativePath(value.transform, _wearableComp.transform); + }); + } + } + + public OneConfArmatureMappingModule(GameObject avatarGameObject, DTWearable wearableComp) + : base(avatarGameObject, wearableComp) + { + } + + public override VisualElement CreateView() + { + throw new NotImplementedException(); + } + } +} diff --git a/Editor/Configurator/Modules/ArmatureMerging/OneConfArmatureMappingModule.cs.meta b/Editor/Configurator/Modules/ArmatureMerging/OneConfArmatureMappingModule.cs.meta new file mode 100644 index 00000000..ca51f749 --- /dev/null +++ b/Editor/Configurator/Modules/ArmatureMerging/OneConfArmatureMappingModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 729a7863d8e76ae479fb19cdb85f4934 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Modules/BlendshapeSync.meta b/Editor/Configurator/Modules/BlendshapeSync.meta new file mode 100644 index 00000000..02233876 --- /dev/null +++ b/Editor/Configurator/Modules/BlendshapeSync.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1c82e513b4c9b69479f8dc293a548e3e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Modules/BlendshapeSync/DTBlendshapeSyncModule.cs b/Editor/Configurator/Modules/BlendshapeSync/DTBlendshapeSyncModule.cs new file mode 100644 index 00000000..3cb200c2 --- /dev/null +++ b/Editor/Configurator/Modules/BlendshapeSync/DTBlendshapeSyncModule.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 Chocopoi.DressingTools.UI.Views; +using UnityEngine.UIElements; + +namespace Chocopoi.DressingTools.Configurator.Modules +{ + internal class DTBlendshapeSyncModule : IBlendshapeSyncModule + { + public VisualElement CreateView() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Editor/Configurator/Modules/BlendshapeSync/DTBlendshapeSyncModule.cs.meta b/Editor/Configurator/Modules/BlendshapeSync/DTBlendshapeSyncModule.cs.meta new file mode 100644 index 00000000..08fbbe26 --- /dev/null +++ b/Editor/Configurator/Modules/BlendshapeSync/DTBlendshapeSyncModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 779e29f9433456b4c8fa889907624dc0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Modules/BlendshapeSync/IBlendshapeSyncModule.cs b/Editor/Configurator/Modules/BlendshapeSync/IBlendshapeSyncModule.cs new file mode 100644 index 00000000..56cca446 --- /dev/null +++ b/Editor/Configurator/Modules/BlendshapeSync/IBlendshapeSyncModule.cs @@ -0,0 +1,20 @@ +/* + * 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.Modules +{ + internal interface IBlendshapeSyncModule : IModule + { + } +} diff --git a/Editor/Configurator/Modules/BlendshapeSync/IBlendshapeSyncModule.cs.meta b/Editor/Configurator/Modules/BlendshapeSync/IBlendshapeSyncModule.cs.meta new file mode 100644 index 00000000..0316d348 --- /dev/null +++ b/Editor/Configurator/Modules/BlendshapeSync/IBlendshapeSyncModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0e01f4f6b8a67ea43bc320e24c7b63b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Modules/BlendshapeSync/MABlendshapeSyncModule.cs b/Editor/Configurator/Modules/BlendshapeSync/MABlendshapeSyncModule.cs new file mode 100644 index 00000000..ea2b4e28 --- /dev/null +++ b/Editor/Configurator/Modules/BlendshapeSync/MABlendshapeSyncModule.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 . + */ +#if DT_MA +using UnityEngine.UIElements; + +namespace Chocopoi.DressingTools.Configurator.Modules +{ + internal class MABlendshapeSyncModule : IBlendshapeSyncModule + { + public VisualElement CreateView() + { + throw new System.NotImplementedException(); + } + } +} +#endif diff --git a/Editor/Configurator/Modules/BlendshapeSync/MABlendshapeSyncModule.cs.meta b/Editor/Configurator/Modules/BlendshapeSync/MABlendshapeSyncModule.cs.meta new file mode 100644 index 00000000..3761cf14 --- /dev/null +++ b/Editor/Configurator/Modules/BlendshapeSync/MABlendshapeSyncModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 38320b0d2808c7149aba1380055e5f92 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Modules/BlendshapeSync/OneConfBlendshapeSyncModule.cs b/Editor/Configurator/Modules/BlendshapeSync/OneConfBlendshapeSyncModule.cs new file mode 100644 index 00000000..db083679 --- /dev/null +++ b/Editor/Configurator/Modules/BlendshapeSync/OneConfBlendshapeSyncModule.cs @@ -0,0 +1,24 @@ +/* + * 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.UIElements; + +namespace Chocopoi.DressingTools.Configurator.Modules +{ + internal class OneConfBlendshapeSyncModule : IBlendshapeSyncModule + { + public VisualElement CreateView() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Editor/Configurator/Modules/BlendshapeSync/OneConfBlendshapeSyncModule.cs.meta b/Editor/Configurator/Modules/BlendshapeSync/OneConfBlendshapeSyncModule.cs.meta new file mode 100644 index 00000000..95d73e70 --- /dev/null +++ b/Editor/Configurator/Modules/BlendshapeSync/OneConfBlendshapeSyncModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5e6173a74c0269040a8d92c9e491f3a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Modules/IModule.cs b/Editor/Configurator/Modules/IModule.cs new file mode 100644 index 00000000..38bf3dcb --- /dev/null +++ b/Editor/Configurator/Modules/IModule.cs @@ -0,0 +1,21 @@ +/* + * 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.UIElements; + +namespace Chocopoi.DressingTools.Configurator.Modules +{ + internal interface IModule + { + VisualElement CreateView(); + } +} diff --git a/Editor/Configurator/Modules/IModule.cs.meta b/Editor/Configurator/Modules/IModule.cs.meta new file mode 100644 index 00000000..bc66abc4 --- /dev/null +++ b/Editor/Configurator/Modules/IModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d66cf2dc76dd3834ea6a6d9b17dd6d52 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Configurator/Modules/OneConfModuleBase.cs b/Editor/Configurator/Modules/OneConfModuleBase.cs new file mode 100644 index 00000000..d05912e5 --- /dev/null +++ b/Editor/Configurator/Modules/OneConfModuleBase.cs @@ -0,0 +1,113 @@ +/* + * 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 Chocopoi.DressingTools.OneConf.Wearable; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Chocopoi.DressingTools.Configurator.Modules +{ + internal abstract class OneConfModuleBase : IModule + { + protected readonly GameObject _avatarGameObject; + protected readonly DTWearable _wearableComp; + + public OneConfModuleBase(GameObject avatarGameObject, DTWearable wearableComp) + { + _avatarGameObject = avatarGameObject; + _wearableComp = wearableComp; + } + + public abstract VisualElement CreateView(); + + protected 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(); + } + } + + protected 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); + } + + protected void ReadWearableConfig(out WearableConfig config) + { + if (!WearableConfigUtility.TryDeserialize(_wearableComp.ConfigJson, out config)) + { + config = null; + } + } + + protected void ReadWearableModule(out T module) where T : IModuleConfig + { + ReadWearableConfig(out var config); + if (config == null) + { + module = default; + return; + } + module = config.FindModuleConfig(); + } + + protected void WriteWearableConfig(Action func) + { + if (!WearableConfigUtility.TryDeserialize(_wearableComp.ConfigJson, out var config)) + { + return; + } + func?.Invoke(config); + _wearableComp.ConfigJson = WearableConfigUtility.Serialize(config); + } + + protected void WriteWearableModule(Action func) where T : IModuleConfig + { + WriteWearableConfig((config) => + { + var module = config.FindModuleConfig(); + if (module == null) + { + return; + } + func?.Invoke(module); + }); + } + } +} diff --git a/Editor/Configurator/Modules/OneConfModuleBase.cs.meta b/Editor/Configurator/Modules/OneConfModuleBase.cs.meta new file mode 100644 index 00000000..223d1672 --- /dev/null +++ b/Editor/Configurator/Modules/OneConfModuleBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 720a49557cbe58b48add646ef9850596 +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/Passes/Cabinet/WardrobePass.cs b/Editor/Passes/Cabinet/WardrobePass.cs index 1edd4187..a4327b1a 100644 --- a/Editor/Passes/Cabinet/WardrobePass.cs +++ b/Editor/Passes/Cabinet/WardrobePass.cs @@ -76,7 +76,7 @@ private static bool LookUpForWardrobe(Transform root, out DTWardrobe wardrobe) return false; } - private static bool LookUpForOutfit(Transform root, out IOutfit outfit) + private static bool LookUpForOutfit(Transform root, out IOutfitComponent outfit) { while (root != null) { @@ -212,7 +212,7 @@ private static void ComposeOutfitItems(Transform parent) } } - private static void CreateOutfitSubMenu(Context ctx, DTWardrobe wardrobeComp, Transform outfitTransform, IOutfit outfit) + private static void CreateOutfitSubMenu(Context ctx, DTWardrobe wardrobeComp, Transform outfitTransform, IOutfitComponent outfit) { if (wardrobeComp.UseAsMenuGroup) { @@ -238,7 +238,7 @@ private static void CreateOutfitSubMenu(Context ctx, DTWardrobe wardrobeComp, Tr } } - private static void CreateOutfitEnableItem(Context ctx, DTWardrobe wardrobeComp, DTParameterSlot slot, Transform outfitTransform, IOutfit outfit, float mappedVal) + private static void CreateOutfitEnableItem(Context ctx, DTWardrobe wardrobeComp, DTParameterSlot slot, Transform outfitTransform, IOutfitComponent outfit, float mappedVal) { if (wardrobeComp.UseAsMenuGroup) { @@ -456,7 +456,7 @@ private static void ComposeOutfitEnableItems(Context ctx, DTParameterSlot slot, if (LookUpForWardrobe(outfitEnableItem.transform, out var wardrobeComp) && !wardrobeComp.UseAsMenuGroup) { // look up for wardrobe. if found, check if not used as menu group - IOutfit outfit; + IOutfitComponent outfit; if (outfitEnableItem.TargetOutfit != null) { outfit = outfitEnableItem.TargetOutfit; diff --git a/Editor/UI/DTMainEditorWindow.cs b/Editor/UI/DTMainEditorWindow.cs index d74fd95e..78042d02 100644 --- a/Editor/UI/DTMainEditorWindow.cs +++ b/Editor/UI/DTMainEditorWindow.cs @@ -17,7 +17,6 @@ using System.Diagnostics.CodeAnalysis; using Chocopoi.DressingFramework.Localization; -using Chocopoi.DressingTools.Components.OneConf; using Chocopoi.DressingTools.Localization; using Chocopoi.DressingTools.UI.Views; using UnityEditor; @@ -46,7 +45,7 @@ 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) { diff --git a/Editor/UI/Presenters/AvatarPresenter.cs b/Editor/UI/Presenters/AvatarPresenter.cs new file mode 100644 index 00000000..6259e3ba --- /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(_view.SelectedAvatarGameObject, outfit.RootTransform.gameObject); + } + }); + } + } + } + + private void UpdateView() + { + UpdateAvatarContentView(); + _view.Repaint(); + } + + private void OnLoad() + { + UpdateView(); + } + + private void OnUnload() + { + UnsubscribeEvents(); + } + + private void OnAddOutfitButtonClick() + { + _view.StartDressing(_view.SelectedAvatarGameObject); + } + } +} 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/DressPresenter.cs b/Editor/UI/Presenters/DressPresenter.cs new file mode 100644 index 00000000..a66626de --- /dev/null +++ b/Editor/UI/Presenters/DressPresenter.cs @@ -0,0 +1,102 @@ +/* + * File: DressingPresenter.cs + * Project: DressingTools + * Created Date: Saturday, August 12th 2023, 1:22:09 am + * 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 Chocopoi.DressingFramework; +using Chocopoi.DressingTools.Components.OneConf; +using Chocopoi.DressingTools.Configurator; +using Chocopoi.DressingTools.Configurator.Cabinet; +using Chocopoi.DressingTools.UI.Views; +using UnityEngine; + +namespace Chocopoi.DressingTools.UI.Presenters +{ + internal class DressPresenter + { + private IDressSubView _view; + + public DressPresenter(IDressSubView view) + { + _view = view; + + SubscribeEvents(); + } + + private void SubscribeEvents() + { + _view.Load += OnLoad; + _view.Unload += OnUnload; + + _view.ForceUpdateView += OnForceUpdateView; + _view.StartButtonClick += OnStartButtonClick; + } + + private void UnsubscribeEvents() + { + _view.Load -= OnLoad; + _view.Unload -= OnUnload; + + _view.ForceUpdateView -= OnForceUpdateView; + _view.StartButtonClick -= OnStartButtonClick; + } + + private void OnForceUpdateView() + { + UpdateView(); + } + + private void OnStartButtonClick() + { + if (_view.SelectedAvatarGameObject == null) + { + Debug.LogError("Avatar is not selected."); + return; + } + if (_view.SelectedOutfitGameObject == null) + { + Debug.LogError("Outfit is not selected."); + return; + } + if (!DKEditorUtils.IsGrandParent(_view.SelectedAvatarGameObject.transform, _view.SelectedOutfitGameObject.transform) && _view.SelectedAvatarGameObject.transform.Find(_view.SelectedOutfitGameObject.name) != null) + { + Debug.LogError("There is already an outfit with the same name in the avatar."); + return; + } + if (!_view.SelectedOutfitGameObject.TryGetComponent(out var oneConfWearableComp)) + { + oneConfWearableComp = _view.SelectedOutfitGameObject.AddComponent(); + } + var outfit = new OneConfConfigurableOutfit(_view.SelectedAvatarGameObject, oneConfWearableComp); + AvatarPreviewUtility.StartAvatarPreview(_view.SelectedAvatarGameObject, outfit); + } + + private void UpdateView() + { + _view.Repaint(); + } + + private void OnLoad() + { + UpdateView(); + } + + private void OnUnload() + { + UnsubscribeEvents(); + } + } +} diff --git a/Editor/UI/Presenters/DressingPresenter.cs.meta b/Editor/UI/Presenters/DressPresenter.cs.meta similarity index 100% rename from Editor/UI/Presenters/DressingPresenter.cs.meta rename to Editor/UI/Presenters/DressPresenter.cs.meta diff --git a/Editor/UI/Presenters/DressingPresenter.cs b/Editor/UI/Presenters/DressingPresenter.cs deleted file mode 100644 index 76272ec8..00000000 --- a/Editor/UI/Presenters/DressingPresenter.cs +++ /dev/null @@ -1,164 +0,0 @@ -/* - * File: DressingPresenter.cs - * Project: DressingTools - * Created Date: Saturday, August 12th 2023, 1:22:09 am - * 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.DressingTools.OneConf; -using Chocopoi.DressingTools.OneConf.Cabinet; -using Chocopoi.DressingTools.OneConf.Serialization; -using Chocopoi.DressingTools.OneConf.Wearable; -using Chocopoi.DressingTools.UI.Views; -using UnityEngine; - -namespace Chocopoi.DressingTools.UI.Presenters -{ - internal class DressingPresenter - { - private IDressingSubView _view; - - public DressingPresenter(IDressingSubView view) - { - _view = view; - - SubscribeEvents(); - } - - private void SubscribeEvents() - { - _view.Load += OnLoad; - _view.Unload += OnUnload; - - _view.ForceUpdateView += OnForceUpdateView; - _view.TargetAvatarOrWearableChange += OnTargetAvatarOrWearableChange; - _view.AddToCabinetButtonClick += OnAddToCabinetButtonClick; - } - - private void UnsubscribeEvents() - { - _view.Load -= OnLoad; - _view.Unload -= OnUnload; - - _view.ForceUpdateView -= OnForceUpdateView; - _view.TargetAvatarOrWearableChange -= OnTargetAvatarOrWearableChange; - _view.AddToCabinetButtonClick -= OnAddToCabinetButtonClick; - } - - private void OnForceUpdateView() - { - UpdateView(); - } - - private void UpdateView() - { - var cabinet = OneConfUtils.GetAvatarCabinet(_view.TargetAvatar); - _view.DisableAddToCabinetButton = cabinet == null; - _view.Repaint(); - } - - private void OnLoad() - { - UpdateView(); - } - - private void OnUnload() - { - UnsubscribeEvents(); - } - - private void CreateNewConfig() - { - if (_view.TargetAvatar == null) - { - return; - } - - _view.Config = new WearableConfig(); - OneConfUtils.PrepareWearableConfig(_view.Config, _view.TargetAvatar, _view.TargetWearable); - _view.ForceUpdateConfigView(); - _view.AutoSetup(); - } - - private void OnTargetAvatarOrWearableChange() - { - // try find if the wearable has a config, if yes, use advanced mode for editing - if (_view.TargetWearable != null) - { - var cabinetWearable = OneConfUtils.GetCabinetWearable(_view.TargetWearable); - if (cabinetWearable != null) - { - if (WearableConfigUtility.TryDeserialize(cabinetWearable.ConfigJson, out var config)) - { - _view.Config = config; - _view.SelectedDressingMode = 1; - _view.ForceUpdateConfigView(); - } - else - { - CreateNewConfig(); - } - } - else - { - CreateNewConfig(); - } - } - - UpdateView(); - } - - private void OnAddToCabinetButtonClick() - { - if (!_view.IsConfigValid()) - { - _view.ShowFixAllInvalidConfig(); - Debug.Log("[DressingTools] Invalid configuration. Cannot proceed adding to cabinet"); - return; - } - - var cabinet = OneConfUtils.GetAvatarCabinet(_view.TargetAvatar); - - if (cabinet == null) - { - return; - } - - if (!CabinetConfigUtility.TryDeserialize(cabinet.ConfigJson, out var cabinetConfig)) - { - // TODO: handle deserialization error - Debug.Log("[DressingTools] Could not deserialize cabinet config"); - return; - } - - // remove previews - DTEditorUtils.CleanUpPreviewAvatars(); - DTEditorUtils.FocusGameObjectInSceneView(_view.TargetAvatar); - - _view.ApplyToConfig(); - - if (cabinet.AddWearable(_view.Config, _view.TargetWearable)) - { - // reset and return - _view.ResetWizardAndConfigView(); - _view.SelectTab(0); - - _view.ForceUpdateCabinetSubView(); - } - else - { - _view.ShowFixAllInvalidConfig(); - } - } - } -} diff --git a/Editor/UI/Presenters/MainPresenter.cs b/Editor/UI/Presenters/MainPresenter.cs index 8efa263e..737b597b 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,80 @@ 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(); + } + + private void UpdateAvatarSelectionDropdown(List avatars) + { + // avatar selection dropdown + _view.AvailableAvatarSelections.Clear(); + for (var i = 0; i < avatars.Count; i++) + { + _view.AvailableAvatarSelections.Add(avatars[i].name); + } + } + public void SelectAvatar(GameObject avatarGameObject) + { + // refresh the keys first + var avatars = AvatarUtils.FindSceneAvatars(SceneManager.GetActiveScene()); + UpdateAvatarSelectionDropdown(avatars); + + // find matching index + for (var i = 0; i < avatars.Count; i++) + { + if (avatars[i] == avatarGameObject) + { + _view.SelectedAvatarIndex = i; + break; + } + } + + // update + UpdateView(); + } + + private void UpdateView() + { + var avatars = AvatarUtils.FindSceneAvatars(SceneManager.GetActiveScene()); + UpdateAvatarSelectionDropdown(avatars); + if (_view.SelectedAvatarIndex < 0 || _view.SelectedAvatarIndex >= avatars.Count) + { + // invalid selected cabinet index, setting it back to 0 + _view.SelectedAvatarIndex = 0; + } + _view.SelectedAvatarGameObject = avatars[_view.SelectedAvatarIndex]; + + _view.ToolVersionText = UpdateChecker.CurrentVersion?.fullString; + + _view.Repaint(); } private void OnPlayModeStateChanged(PlayModeStateChange change) @@ -123,6 +203,7 @@ private void OnPrefabStageClosing(PrefabStage stage) private void OnLoad() { + UpdateView(); } private void OnUnload() diff --git a/Editor/UI/Views/AvatarSubView.cs b/Editor/UI/Views/AvatarSubView.cs new file mode 100644 index 00000000..e74db370 --- /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 GameObject SelectedAvatarGameObject { get => _mainView.SelectedAvatarGameObject; set => _mainView.SelectedAvatarGameObject = value; } + public int SettingsAnimationWriteDefaultsMode { get; set; } + public List InstalledOutfitPreviews { get; set; } + + 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 avatarGameObject = null, GameObject wearableGameObject = null) + { + _mainView.StartDressing(avatarGameObject != null ? avatarGameObject : SelectedAvatarGameObject, wearableGameObject); + } + + 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