diff --git a/samples/Unity.Mvvm.MainMenu/Assets/Scenes/MainScene.unity b/samples/Unity.Mvvm.MainMenu/Assets/Scenes/MainScene.unity index 62bc3c6..912d13f 100644 --- a/samples/Unity.Mvvm.MainMenu/Assets/Scenes/MainScene.unity +++ b/samples/Unity.Mvvm.MainMenu/Assets/Scenes/MainScene.unity @@ -134,7 +134,6 @@ GameObject: - component: {fileID: 519420032} - component: {fileID: 519420031} - component: {fileID: 519420029} - - component: {fileID: 519420033} m_Layer: 0 m_Name: MainCamera m_TagString: MainCamera @@ -208,15 +207,64 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &519420033 +--- !u!1 &1044909860 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1044909862} + - component: {fileID: 1044909861} + - component: {fileID: 1044909863} + m_Layer: 5 + m_Name: UIDocument + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1044909861 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 519420028} + m_GameObject: {fileID: 1044909860} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 19102, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_PanelSettings: {fileID: 11400000, guid: 05a4f1b7e240fdf4da6d25c89caa99e3, type: 2} + m_ParentUI: {fileID: 0} + sourceAsset: {fileID: 9197481963319205126, guid: 3331342ac7198664a90d50dfe90030fa, type: 3} + m_SortingOrder: 0 +--- !u!4 &1044909862 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1044909860} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1044909863 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1044909860} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 0a9a6d990dc283e4d91d57e902903ec0, type: 3} + m_Script: {fileID: 11500000, guid: 9b62dba053e944deb41f3b81e40f1d1e, type: 3} m_Name: m_EditorClassIdentifier: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/Scripts/HelloFromSourceGenerator.cs b/samples/Unity.Mvvm.MainMenu/Assets/Scripts/HelloFromSourceGenerator.cs deleted file mode 100644 index 6e6d3b7..0000000 --- a/samples/Unity.Mvvm.MainMenu/Assets/Scripts/HelloFromSourceGenerator.cs +++ /dev/null @@ -1,14 +0,0 @@ -using UnityEngine; - -public class HelloFromSourceGenerator : MonoBehaviour -{ - static string GetStringFromSourceGenerator() - { - return ExampleSourceGenerated.ExampleSourceGenerated.GetTestText(); - } - - private void Start() - { - print(GetStringFromSourceGenerator()); - } -} diff --git a/samples/Unity.Mvvm.MainMenu/Assets/Scripts/ViewModels.meta b/samples/Unity.Mvvm.MainMenu/Assets/Scripts/ViewModels.meta new file mode 100644 index 0000000..e9af18e --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/Scripts/ViewModels.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6632b49d12934814a5933474cb73b160 +timeCreated: 1657791554 \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/Scripts/ViewModels/MainMenuViewModel.cs b/samples/Unity.Mvvm.MainMenu/Assets/Scripts/ViewModels/MainMenuViewModel.cs new file mode 100644 index 0000000..1c8c4c8 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/Scripts/ViewModels/MainMenuViewModel.cs @@ -0,0 +1,22 @@ +using UnityMvvmToolkit.Common; + +namespace ViewModels +{ + public class MainMenuViewModel : ViewModel + { + private string _strValue; + private string _strValue1; + + public string StrValue + { + get => _strValue; + set => Set(ref _strValue, value); + } + + public string StrValue1 + { + get => _strValue1; + set => Set(ref _strValue1, value); + } + } +} \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/Scripts/ViewModels/MainMenuViewModel.cs.meta b/samples/Unity.Mvvm.MainMenu/Assets/Scripts/ViewModels/MainMenuViewModel.cs.meta new file mode 100644 index 0000000..304b964 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/Scripts/ViewModels/MainMenuViewModel.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ab138b4ab29841abbb0555baea2141d9 +timeCreated: 1657791695 \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/Scripts/Views.meta b/samples/Unity.Mvvm.MainMenu/Assets/Scripts/Views.meta new file mode 100644 index 0000000..6264b5e --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/Scripts/Views.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 331ce41a95784c068cca15a573cadf2c +timeCreated: 1657791547 \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/Scripts/Views/MainMenuView.cs b/samples/Unity.Mvvm.MainMenu/Assets/Scripts/Views/MainMenuView.cs new file mode 100644 index 0000000..affb908 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/Scripts/Views/MainMenuView.cs @@ -0,0 +1,90 @@ +using System; +using System.ComponentModel; +using UnityMvvmToolkit.Common.Attributes; +using UnityMvvmToolkit.UI; +using ViewModels; + +using UnityEngine.UIElements; +using System.Collections.Generic; +using UnityMvvmToolkit.UI.BindableVisualElements; + +namespace Views +{ + // [VisualTreeAsset("UI Toolkit/MainMenu.uxml")] + public partial class MainMenuView : View + { + protected override MainMenuViewModel GetBindingContext() + { + return new MainMenuViewModel { StrValue = "Test String!!!", StrValue1 = "It works!!!" }; + } + } + + public partial class MainMenuView + { + private BindableLabel _label0; + private BindableTextField _textField0; + private BindableTextField _textField1; + + private Dictionary _updateValuesAction; + + protected override void BindElements(MainMenuViewModel bindingContext, VisualElement rootVisualElement) + { + _label0 = rootVisualElement.Q("label0"); + + _textField0 = rootVisualElement.Q("textField0"); + _textField0.RegisterValueChangedCallback(OnUpdateStrValue); + + _textField1 = rootVisualElement.Q("textField1"); + _textField1.RegisterValueChangedCallback(OnUpdateStrValue1); + + _updateValuesAction = new Dictionary + { + { "StrValue", UpdateStrValueSubscribers }, + { "StrValue1", UpdateStrValue1Subscribers } + }; + + UpdateStrValueSubscribers(); + UpdateStrValue1Subscribers(); + } + + private void OnUpdateStrValue(ChangeEvent e) + { + BindingContext.StrValue = e.newValue; + } + + private void OnUpdateStrValue1(ChangeEvent e) + { + BindingContext.StrValue1 = e.newValue; + } + + private void UpdateStrValueSubscribers() + { + var newValue = BindingContext.StrValue; + + if (_label0.text != newValue) + { + _label0.text = newValue; + } + + if (_textField0.value != newValue) + { + _textField0.SetValueWithoutNotify(newValue); + } + } + + private void UpdateStrValue1Subscribers() + { + var newValue = BindingContext.StrValue1; + + if (_textField1.value != newValue) + { + _textField1.SetValueWithoutNotify(newValue); + } + } + + protected override void OnBindingContextPropertyChanged(object sender, PropertyChangedEventArgs e) + { + _updateValuesAction[e.PropertyName](); + } + } +} \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/Scripts/Views/MainMenuView.cs.meta b/samples/Unity.Mvvm.MainMenu/Assets/Scripts/Views/MainMenuView.cs.meta new file mode 100644 index 0000000..29d337d --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/Scripts/Views/MainMenuView.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9b62dba053e944deb41f3b81e40f1d1e +timeCreated: 1657791685 \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit.meta b/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit.meta new file mode 100644 index 0000000..3964970 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 52a48eb306f4912419b720b0ac4c59b8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/MainMenu.uxml b/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/MainMenu.uxml new file mode 100644 index 0000000..1f330db --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/MainMenu.uxml @@ -0,0 +1,5 @@ + + + + + diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/MainMenu.uxml.meta b/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/MainMenu.uxml.meta new file mode 100644 index 0000000..4d9b5ab --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/MainMenu.uxml.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 3331342ac7198664a90d50dfe90030fa +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0} diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/PanelSettings.asset b/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/PanelSettings.asset new file mode 100644 index 0000000..ef378ba --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/PanelSettings.asset @@ -0,0 +1,37 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 19101, guid: 0000000000000000e000000000000000, type: 0} + m_Name: PanelSettings + m_EditorClassIdentifier: + themeUss: {fileID: -4733365628477956816, guid: 9a71bae960d624d44acdf8bbcb7d583d, type: 3} + m_TargetTexture: {fileID: 0} + m_ScaleMode: 1 + m_Scale: 1 + m_ReferenceDpi: 120 + m_FallbackDpi: 96 + m_ReferenceResolution: {x: 1200, y: 800} + m_ScreenMatchMode: 0 + m_Match: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 + m_ClearDepthStencil: 1 + m_ClearColor: 0 + m_ColorClearValue: {r: 0, g: 0, b: 0, a: 0} + m_DynamicAtlasSettings: + m_MinAtlasSize: 64 + m_MaxAtlasSize: 4096 + m_MaxSubTextureSize: 64 + m_ActiveFilters: 31 + m_AtlasBlitShader: {fileID: 9101, guid: 0000000000000000f000000000000000, type: 0} + m_RuntimeShader: {fileID: 9100, guid: 0000000000000000f000000000000000, type: 0} + m_RuntimeWorldShader: {fileID: 9102, guid: 0000000000000000f000000000000000, type: 0} + textSettings: {fileID: 0} diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/PanelSettings.asset.meta b/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/PanelSettings.asset.meta new file mode 100644 index 0000000..ebe2a9e --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/PanelSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 05a4f1b7e240fdf4da6d25c89caa99e3 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/UnityThemes.meta b/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/UnityThemes.meta new file mode 100644 index 0000000..6ce3519 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/UnityThemes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: edce8b7e8ea4b214e8975b19e4d3172c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/UnityThemes/UnityDefaultRuntimeTheme.tss.meta b/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/UnityThemes/UnityDefaultRuntimeTheme.tss.meta new file mode 100644 index 0000000..820cd7a --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UI Toolkit/UnityThemes/UnityDefaultRuntimeTheme.tss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9a71bae960d624d44acdf8bbcb7d583d +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12388, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit.meta new file mode 100644 index 0000000..91ac310 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 29b0e72167ffd92489591aa499884528 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common.meta new file mode 100644 index 0000000..6f059a1 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4942c783af6eef54faeb35b5f26d6525 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes.meta new file mode 100644 index 0000000..03e17fd --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 02ac014099ac8b54784c32293a6e7234 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes/BindToAttribute.cs b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes/BindToAttribute.cs new file mode 100644 index 0000000..b570517 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes/BindToAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace UnityMvvmToolkit.Common.Attributes +{ + [AttributeUsage(AttributeTargets.Property)] + public sealed class BindToAttribute : Attribute + { + public BindToAttribute(string targetPropertyName) // TODO: One or two way (set from uxml). + { + TargetPropertyName = targetPropertyName; + } + + public string TargetPropertyName { get; } + } +} \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/Scripts/HelloFromSourceGenerator.cs.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes/BindToAttribute.cs.meta similarity index 83% rename from samples/Unity.Mvvm.MainMenu/Assets/Scripts/HelloFromSourceGenerator.cs.meta rename to samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes/BindToAttribute.cs.meta index 5fab6ea..6e58fd9 100644 --- a/samples/Unity.Mvvm.MainMenu/Assets/Scripts/HelloFromSourceGenerator.cs.meta +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes/BindToAttribute.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 0a9a6d990dc283e4d91d57e902903ec0 +guid: 55bd311c0f7d06e46bd9c8ea15060c60 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes/VisualTreeAssetAttribute.cs b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes/VisualTreeAssetAttribute.cs new file mode 100644 index 0000000..0bf1098 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes/VisualTreeAssetAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace UnityMvvmToolkit.Common.Attributes +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class VisualTreeAssetAttribute : Attribute + { + public VisualTreeAssetAttribute(string assetPath) + { + AssetPath = assetPath; + } + + public string AssetPath { get; } + } +} \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes/VisualTreeAssetAttribute.cs.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes/VisualTreeAssetAttribute.cs.meta new file mode 100644 index 0000000..c986c12 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Attributes/VisualTreeAssetAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 17f4537bf3018094eac204f27cc6e5c6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces.meta new file mode 100644 index 0000000..a69e110 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 11c353a1b6a71e544b238dd04fc80d53 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IBindableVisualElement.cs b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IBindableVisualElement.cs new file mode 100644 index 0000000..594686e --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IBindableVisualElement.cs @@ -0,0 +1,6 @@ +namespace UnityMvvmToolkit.Common.Interfaces +{ + public interface IBindableVisualElement + { + } +} \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IBindableVisualElement.cs.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IBindableVisualElement.cs.meta new file mode 100644 index 0000000..27cc462 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IBindableVisualElement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3eb18b14cca137445bc1d90b3a0a0e03 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IView.cs b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IView.cs new file mode 100644 index 0000000..a5abded --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IView.cs @@ -0,0 +1,6 @@ +namespace UnityMvvmToolkit.Common.Interfaces +{ + public interface IView + { + } +} \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IView.cs.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IView.cs.meta new file mode 100644 index 0000000..71c9e16 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e49e94a2c1e3cc34498da10f68616df6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IVisualElementBindings.cs b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IVisualElementBindings.cs new file mode 100644 index 0000000..69abbf1 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IVisualElementBindings.cs @@ -0,0 +1,7 @@ +namespace UnityMvvmToolkit.Common.Interfaces +{ + public interface IVisualElementBindings + { + void UpdateValues(); + } +} \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IVisualElementBindings.cs.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IVisualElementBindings.cs.meta new file mode 100644 index 0000000..c19e26b --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/Interfaces/IVisualElementBindings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 28512b0d243524e498df18ba702abae3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/ViewModel.cs b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/ViewModel.cs new file mode 100644 index 0000000..f875b65 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/ViewModel.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace UnityMvvmToolkit.Common +{ + public abstract class ViewModel : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected bool Set(ref T oldValue, T newValue, [CallerMemberName] string propertyName = default) + { + if (EqualityComparer.Default.Equals(oldValue, newValue)) + { + return false; + } + + oldValue = newValue; + OnPropertyChanged(propertyName); + + return true; + } + + protected bool Set(T oldValue, T newValue, TModel model, Action callback, + [CallerMemberName] string propertyName = default) where TModel : class + { + if (EqualityComparer.Default.Equals(oldValue, newValue)) + { + return false; + } + + callback(model, newValue); + OnPropertyChanged(propertyName); + + return true; + } + + protected void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/ViewModel.cs.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/ViewModel.cs.meta new file mode 100644 index 0000000..66dfcb5 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/Common/ViewModel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cf160a9f81dde94409454031687c7677 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI.meta new file mode 100644 index 0000000..75c5e4a --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5deea0ee0d2964645b8f04407d1efc8b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements.meta new file mode 100644 index 0000000..51a6368 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9a6a365fc19c6e348b70814faf7ada1c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements/BindableLabel.cs b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements/BindableLabel.cs new file mode 100644 index 0000000..2aa7581 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements/BindableLabel.cs @@ -0,0 +1,29 @@ +using UnityEngine.UIElements; +using UnityMvvmToolkit.Common.Attributes; +using UnityMvvmToolkit.Common.Interfaces; + +namespace UnityMvvmToolkit.UI.BindableVisualElements +{ + public class BindableLabel : Label, IBindableVisualElement + { + [BindTo(nameof(text))] + public string BindingTextPath { get; set; } + + public new class UxmlFactory : UxmlFactory + { + } + + public new class UxmlTraits : Label.UxmlTraits + { + private readonly UxmlStringAttributeDescription _bindingTextAttribute = new() + { name = "binding-text-path", defaultValue = "binding-property-name" }; + + public override void Init(VisualElement visualElement, IUxmlAttributes bag, CreationContext context) + { + base.Init(visualElement, bag, context); + ((BindableLabel) visualElement).BindingTextPath = + _bindingTextAttribute.GetValueFromBag(bag, context); + } + } + } +} \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements/BindableLabel.cs.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements/BindableLabel.cs.meta new file mode 100644 index 0000000..036cc05 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements/BindableLabel.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dd55009627c7475ba7f9a364c7292a5b +timeCreated: 1657789669 \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements/BindableTextField.cs b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements/BindableTextField.cs new file mode 100644 index 0000000..9039d52 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements/BindableTextField.cs @@ -0,0 +1,29 @@ +using UnityEngine.UIElements; +using UnityMvvmToolkit.Common.Attributes; +using UnityMvvmToolkit.Common.Interfaces; + +namespace UnityMvvmToolkit.UI.BindableVisualElements +{ + public class BindableTextField : TextField, IBindableVisualElement + { + [BindTo(nameof(value))] + public string BindingValuePath { get; set; } + + public new class UxmlFactory : UxmlFactory + { + } + + public new class UxmlTraits : TextField.UxmlTraits + { + private readonly UxmlStringAttributeDescription _bindingValueAttribute = new() + { name = "binding-value-path", defaultValue = "binding-property-name" }; + + public override void Init(VisualElement visualElement, IUxmlAttributes bag, CreationContext context) + { + base.Init(visualElement, bag, context); + ((BindableTextField) visualElement).BindingValuePath = + _bindingValueAttribute.GetValueFromBag(bag, context); + } + } + } +} \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements/BindableTextField.cs.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements/BindableTextField.cs.meta new file mode 100644 index 0000000..ea235c6 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/BindableVisualElements/BindableTextField.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 028dbab7fa75404b94ae26cdedd4a170 +timeCreated: 1657779189 \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/View.cs b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/View.cs new file mode 100644 index 0000000..b44c6d2 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/View.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using UnityEngine; +using UnityEngine.UIElements; +using UnityMvvmToolkit.Common.Interfaces; + +namespace UnityMvvmToolkit.UI +{ + [RequireComponent(typeof(UIDocument))] + public abstract class View : MonoBehaviour where TBindingContext : class, INotifyPropertyChanged, new() + { + private UIDocument _uiDocument; + private TBindingContext _bindingContext; + private List _disposables; + private Dictionary> _visualElementsBindings; + + protected TBindingContext BindingContext => _bindingContext; + + private void Awake() + { + _uiDocument = GetComponent(); + _bindingContext = GetBindingContext(); + + _disposables = new List(); + _visualElementsBindings = new Dictionary>(); + + BindElements(_bindingContext, _uiDocument.rootVisualElement); + } + + private void OnEnable() + { + _bindingContext.PropertyChanged += OnBindingContextPropertyChanged; + } + + private void OnDisable() + { + _bindingContext.PropertyChanged -= OnBindingContextPropertyChanged; + } + + private void OnDestroy() + { + // TODO: Unregister bindable elements (like TextField). + + foreach (var disposable in _disposables) + { + disposable.Dispose(); + } + } + + protected virtual TBindingContext GetBindingContext() + { + return new TBindingContext(); // TODO: Change DataContext dynamically? + } + + protected virtual IVisualElementBindings GetVisualElementBindings(TBindingContext bindingContext, + IBindableVisualElement bindableElement) + { + throw new NotImplementedException(); + } + + protected virtual void BindElements(TBindingContext bindingContext, VisualElement rootVisualElement) + { + // var bindingContextProperties = + // typeof(TBindingContext).GetProperties(BindingFlags.Public | BindingFlags.Instance | + // BindingFlags.DeclaredOnly); + + rootVisualElement.Query().ForEach(visualElement => + { + if (visualElement is IBindableVisualElement bindableElement) + { + RegisterBindableElement(bindableElement); + } + }); + } + + private void RegisterBindableElement(IBindableVisualElement bindableElement) + { + var bindingProperties = bindableElement.GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + + foreach (var bindingPropertyInfo in bindingProperties) + { + var sourcePropertyName = bindingPropertyInfo.GetValue(bindableElement).ToString(); + if (string.IsNullOrWhiteSpace(sourcePropertyName)) + { + continue; + } + + var sourcePropertyInfo = typeof(TBindingContext).GetProperty(sourcePropertyName); // TODO: Cache properties to dictionary. + if (sourcePropertyInfo == null) + { + throw new NullReferenceException(nameof(sourcePropertyInfo)); + } + + var visualElementBindings = GetVisualElementBindings(_bindingContext, bindableElement); + if (visualElementBindings == null) + { + return; + throw new NullReferenceException(nameof(visualElementBindings)); + } + + if (visualElementBindings is IDisposable disposable) + { + _disposables.Add(disposable); + } + + if (_visualElementsBindings.TryGetValue(sourcePropertyName, out var visualElements) == false) + { + visualElements = new HashSet(); + _visualElementsBindings.Add(sourcePropertyName, visualElements); + } + + visualElementBindings.UpdateValues(); + visualElements.Add(visualElementBindings); + } + } + protected virtual void OnBindingContextPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (_visualElementsBindings.TryGetValue(e.PropertyName, out var visualElements)) + { + foreach (var visualElement in visualElements) + { + visualElement.UpdateValues(); + } + } + } + } +} \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/View.cs.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/View.cs.meta new file mode 100644 index 0000000..0f5d939 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UI/View.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0c23133e65c84117a6d710966201599b +timeCreated: 1657793186 \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UnityMvvmToolkit.SourceGenerators.dll b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UnityMvvmToolkit.SourceGenerators.dll new file mode 100644 index 0000000..f653038 Binary files /dev/null and b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UnityMvvmToolkit.SourceGenerators.dll differ diff --git a/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UnityMvvmToolkit.SourceGenerators.dll.meta b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UnityMvvmToolkit.SourceGenerators.dll.meta new file mode 100644 index 0000000..7515c5d --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/Assets/UnityMvvmToolkit/UnityMvvmToolkit.SourceGenerators.dll.meta @@ -0,0 +1,71 @@ +fileFormatVersion: 2 +guid: 25457824e2a0edb4c97c257322a6b106 +labels: +- RoslynAnalyzer +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/Unity.Mvvm.MainMenu/Packages/manifest.json b/samples/Unity.Mvvm.MainMenu/Packages/manifest.json index c148722..9e02b62 100644 --- a/samples/Unity.Mvvm.MainMenu/Packages/manifest.json +++ b/samples/Unity.Mvvm.MainMenu/Packages/manifest.json @@ -1,6 +1,5 @@ { "dependencies": { - "com.chebanovdd.unitymvvmtoolkit": "file:../../../src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit", "com.unity.collab-proxy": "1.15.18", "com.unity.feature.2d": "1.0.0", "com.unity.ide.rider": "3.0.15", diff --git a/samples/Unity.Mvvm.MainMenu/Packages/packages-lock.json b/samples/Unity.Mvvm.MainMenu/Packages/packages-lock.json index 6925dcd..c155a50 100644 --- a/samples/Unity.Mvvm.MainMenu/Packages/packages-lock.json +++ b/samples/Unity.Mvvm.MainMenu/Packages/packages-lock.json @@ -1,11 +1,5 @@ { "dependencies": { - "com.chebanovdd.unitymvvmtoolkit": { - "version": "file:../../../src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit", - "depth": 0, - "source": "local", - "dependencies": {} - }, "com.unity.2d.animation": { "version": "7.0.6", "depth": 1, diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UIElements.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UIElements.xsd new file mode 100644 index 0000000..a966e0f --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UIElements.xsd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.Cloud.Collaborate.Components.ChangeListEntries.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.Cloud.Collaborate.Components.ChangeListEntries.xsd new file mode 100644 index 0000000..37db2cb --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.Cloud.Collaborate.Components.ChangeListEntries.xsd @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.Cloud.Collaborate.Components.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.Cloud.Collaborate.Components.xsd new file mode 100644 index 0000000..06b7da5 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.Cloud.Collaborate.Components.xsd @@ -0,0 +1,332 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.Cloud.Collaborate.Views.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.Cloud.Collaborate.Views.xsd new file mode 100644 index 0000000..f6ec2fb --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.Cloud.Collaborate.Views.xsd @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.Profiling.Editor.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.Profiling.Editor.xsd new file mode 100644 index 0000000..32d7ed8 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.Profiling.Editor.xsd @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.UI.Builder.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.UI.Builder.xsd new file mode 100644 index 0000000..0de8a5b --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/Unity.UI.Builder.xsd @@ -0,0 +1,953 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.Experimental.GraphView.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.Experimental.GraphView.xsd new file mode 100644 index 0000000..f9b22e3 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.Experimental.GraphView.xsd @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.Overlays.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.Overlays.xsd new file mode 100644 index 0000000..d60844f --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.Overlays.xsd @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.PackageManager.UI.Internal.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.PackageManager.UI.Internal.xsd new file mode 100644 index 0000000..665abd5 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.PackageManager.UI.Internal.xsd @@ -0,0 +1,584 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.Search.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.Search.xsd new file mode 100644 index 0000000..216cd6d --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.Search.xsd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.ShortcutManagement.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.ShortcutManagement.xsd new file mode 100644 index 0000000..400097b --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.ShortcutManagement.xsd @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.U2D.Animation.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.U2D.Animation.xsd new file mode 100644 index 0000000..0e7f361 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.U2D.Animation.xsd @@ -0,0 +1,340 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.U2D.Layout.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.U2D.Layout.xsd new file mode 100644 index 0000000..b40a7ff --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.U2D.Layout.xsd @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.UIElements.Debugger.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.UIElements.Debugger.xsd new file mode 100644 index 0000000..a2be34b --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.UIElements.Debugger.xsd @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.UIElements.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.UIElements.xsd new file mode 100644 index 0000000..d16ef98 --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEditor.UIElements.xsd @@ -0,0 +1,896 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEngine.UIElements.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEngine.UIElements.xsd new file mode 100644 index 0000000..60d663f --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityEngine.UIElements.xsd @@ -0,0 +1,896 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityMvvmToolkit.UI.BindableVisualElements.xsd b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityMvvmToolkit.UI.BindableVisualElements.xsd new file mode 100644 index 0000000..939654f --- /dev/null +++ b/samples/Unity.Mvvm.MainMenu/UIElementsSchema/UnityMvvmToolkit.UI.BindableVisualElements.xsd @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/UnityMvvmToolkit.Common/Attributes/VisualTreeAssetAttribute.cs b/src/UnityMvvmToolkit.Common/Attributes/VisualTreeAssetAttribute.cs new file mode 100644 index 0000000..0bf1098 --- /dev/null +++ b/src/UnityMvvmToolkit.Common/Attributes/VisualTreeAssetAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace UnityMvvmToolkit.Common.Attributes +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class VisualTreeAssetAttribute : Attribute + { + public VisualTreeAssetAttribute(string assetPath) + { + AssetPath = assetPath; + } + + public string AssetPath { get; } + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.Common/Interfaces/IView.cs b/src/UnityMvvmToolkit.Common/Interfaces/IView.cs new file mode 100644 index 0000000..a5abded --- /dev/null +++ b/src/UnityMvvmToolkit.Common/Interfaces/IView.cs @@ -0,0 +1,6 @@ +namespace UnityMvvmToolkit.Common.Interfaces +{ + public interface IView + { + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.Common/Interfaces/IVisualElementBindings.cs b/src/UnityMvvmToolkit.Common/Interfaces/IVisualElementBindings.cs new file mode 100644 index 0000000..69abbf1 --- /dev/null +++ b/src/UnityMvvmToolkit.Common/Interfaces/IVisualElementBindings.cs @@ -0,0 +1,7 @@ +namespace UnityMvvmToolkit.Common.Interfaces +{ + public interface IVisualElementBindings + { + void UpdateValues(); + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.Common/UnityMvvmToolkit.Common.csproj b/src/UnityMvvmToolkit.Common/UnityMvvmToolkit.Common.csproj index 3a5e23a..af1a574 100644 --- a/src/UnityMvvmToolkit.Common/UnityMvvmToolkit.Common.csproj +++ b/src/UnityMvvmToolkit.Common/UnityMvvmToolkit.Common.csproj @@ -7,7 +7,7 @@ - $(ProjectDir)..\UnityMvvmToolkit.UnityPackage\Assets\Plugins\UnityMvvmToolkit\Runtime\ + $(ProjectDir)..\..\samples\Unity.Mvvm.MainMenu\Assets\UnityMvvmToolkit\ diff --git a/src/UnityMvvmToolkit.Common/ViewModel.cs b/src/UnityMvvmToolkit.Common/ViewModel.cs new file mode 100644 index 0000000..f875b65 --- /dev/null +++ b/src/UnityMvvmToolkit.Common/ViewModel.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace UnityMvvmToolkit.Common +{ + public abstract class ViewModel : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected bool Set(ref T oldValue, T newValue, [CallerMemberName] string propertyName = default) + { + if (EqualityComparer.Default.Equals(oldValue, newValue)) + { + return false; + } + + oldValue = newValue; + OnPropertyChanged(propertyName); + + return true; + } + + protected bool Set(T oldValue, T newValue, TModel model, Action callback, + [CallerMemberName] string propertyName = default) where TModel : class + { + if (EqualityComparer.Default.Equals(oldValue, newValue)) + { + return false; + } + + callback(model, newValue); + OnPropertyChanged(propertyName); + + return true; + } + + protected void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.SourceGenerators/Captures/BindableElementCapture.cs b/src/UnityMvvmToolkit.SourceGenerators/Captures/BindableElementCapture.cs new file mode 100644 index 0000000..b2e17cc --- /dev/null +++ b/src/UnityMvvmToolkit.SourceGenerators/Captures/BindableElementCapture.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace UnityMvvmToolkit.SourceGenerators.Captures; + +public class BindableElementCapture +{ + public BindableElementCapture(ClassDeclarationSyntax @class) + { + Class = @class; + Properties = new Dictionary(); + } + + public string ClassIdentifier => Class.Identifier.Text; + public ClassDeclarationSyntax Class { get; } + public Dictionary Properties { get; } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.SourceGenerators/Captures/ViewCapture.cs b/src/UnityMvvmToolkit.SourceGenerators/Captures/ViewCapture.cs new file mode 100644 index 0000000..cc1f824 --- /dev/null +++ b/src/UnityMvvmToolkit.SourceGenerators/Captures/ViewCapture.cs @@ -0,0 +1,17 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace UnityMvvmToolkit.SourceGenerators.Captures; + +public class ViewCapture +{ + public ViewCapture(string assetPath, string viewModelIdentifier, ClassDeclarationSyntax @class) + { + AssetPath = assetPath; + ViewModelIdentifier = viewModelIdentifier; + Class = @class; + } + + public string AssetPath { get; } + public string ViewModelIdentifier { get; } + public ClassDeclarationSyntax Class { get; } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.SourceGenerators/ExampleSourceGenerator.cs b/src/UnityMvvmToolkit.SourceGenerators/ExampleSourceGenerator.cs deleted file mode 100644 index 0a9e0d9..0000000 --- a/src/UnityMvvmToolkit.SourceGenerators/ExampleSourceGenerator.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Globalization; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using UnityMvvmToolkit.Common.Attributes; - -namespace UnityMvvmToolkit.SourceGenerators -{ - [Generator] - public class ExampleSourceGenerator : ISourceGenerator - { - public void Initialize(GeneratorInitializationContext context) - { - } - - public void Execute(GeneratorExecutionContext context) - { - - var stringBuilder = new StringBuilder( - @" - using System; - namespace ExampleSourceGenerated - { - public static class ExampleSourceGenerated - { - public static string GetTestText() - { - return ""This is from source generator "); - - stringBuilder.Append(nameof(BindToAttribute)); - - stringBuilder.Append( - @"""; - } - } -} -"); - - context.AddSource("exampleSourceGenerator", SourceText.From(stringBuilder.ToString(), Encoding.UTF8)); - } - } -} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.SourceGenerators/Extensions/SyntaxNodeExtensions.cs b/src/UnityMvvmToolkit.SourceGenerators/Extensions/SyntaxNodeExtensions.cs new file mode 100644 index 0000000..65e3d7d --- /dev/null +++ b/src/UnityMvvmToolkit.SourceGenerators/Extensions/SyntaxNodeExtensions.cs @@ -0,0 +1,23 @@ +using Microsoft.CodeAnalysis; + +namespace UnityMvvmToolkit.SourceGenerators.Extensions; + +public static class SyntaxNodeExtensions +{ + public static T GetParent(this SyntaxNode syntaxNode) + { + var parent = syntaxNode.Parent; + + while (parent != null) + { + if (parent is T result) + { + return result; + } + + parent = parent.Parent; + } + + return default; + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.SourceGenerators/Models/VisualTreeElementInfo.cs b/src/UnityMvvmToolkit.SourceGenerators/Models/VisualTreeElementInfo.cs new file mode 100644 index 0000000..93aa97e --- /dev/null +++ b/src/UnityMvvmToolkit.SourceGenerators/Models/VisualTreeElementInfo.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace UnityMvvmToolkit.SourceGenerators.Models; + +public class VisualTreeElementInfo +{ + public VisualTreeElementInfo(string classIdentifier) + { + ClassIdentifier = classIdentifier; + Attributes = new List>(); + } + + public string ClassIdentifier { get; } + public List> Attributes { get; } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.SourceGenerators/SyntaxReceivers/BindableElementsReceiver.cs b/src/UnityMvvmToolkit.SourceGenerators/SyntaxReceivers/BindableElementsReceiver.cs new file mode 100644 index 0000000..96c7393 --- /dev/null +++ b/src/UnityMvvmToolkit.SourceGenerators/SyntaxReceivers/BindableElementsReceiver.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using UnityMvvmToolkit.Common.Interfaces; +using UnityMvvmToolkit.SourceGenerators.Captures; +using UnityMvvmToolkit.SourceGenerators.Extensions; + +namespace UnityMvvmToolkit.SourceGenerators.SyntaxReceivers; + +public class BindableElementsReceiver : ISyntaxReceiver +{ + private const string AttributeName = "BindTo"; + private const string InterfaceName = nameof(IBindableVisualElement); + + private readonly Dictionary _captures = new(); + + public IReadOnlyDictionary Captures => _captures; + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is not AttributeSyntax + { + Name: IdentifierNameSyntax { Identifier.Text: AttributeName } + } attribute) + { + return; + } + + var bindToPath = GetAttributeArgumentValue(attribute); + if (string.IsNullOrWhiteSpace(bindToPath)) + { + return; + } + + var property = attribute.GetParent(); + var @class = property.GetParent(); + + if (IsImplementInterface(@class, InterfaceName) == false) + { + return; + } + + if (_captures.TryGetValue(@class.Identifier.Text, out var bindableElement) == false) + { + bindableElement = new BindableElementCapture(@class); + _captures.Add(@class.Identifier.Text, bindableElement); + } + + // bindableElement.Properties.Add(new KeyValuePair(bindToPath, property)); + bindableElement.Properties.Add(property.Identifier.Text.ToLower(), bindToPath); + } + + private string GetAttributeArgumentValue(AttributeSyntax attribute) + { + return attribute.ArgumentList?.Arguments.Single().Expression switch + { + LiteralExpressionSyntax literal => literal.Token.ValueText, + InvocationExpressionSyntax invocation => invocation.ArgumentList.Arguments.Single().Expression.GetText().ToString(), + _ => null + }; + } + + private bool IsImplementInterface(ClassDeclarationSyntax @class, string @interface) + { + return @class.BaseList == null || + @class.BaseList.Types.Select(typeSyntax => typeSyntax.Type.GetText().ToString() == @interface).Any(); + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.SourceGenerators/SyntaxReceivers/ViewBindingsReceiver.cs b/src/UnityMvvmToolkit.SourceGenerators/SyntaxReceivers/ViewBindingsReceiver.cs new file mode 100644 index 0000000..5a4d6da --- /dev/null +++ b/src/UnityMvvmToolkit.SourceGenerators/SyntaxReceivers/ViewBindingsReceiver.cs @@ -0,0 +1,15 @@ +using Microsoft.CodeAnalysis; + +namespace UnityMvvmToolkit.SourceGenerators.SyntaxReceivers; + +public class ViewBindingsReceiver : ISyntaxReceiver +{ + public ViewsReceiver Views { get; } = new(); + public BindableElementsReceiver BindableElements { get; } = new(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + BindableElements.OnVisitSyntaxNode(syntaxNode); + Views.OnVisitSyntaxNode(syntaxNode); + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.SourceGenerators/SyntaxReceivers/ViewsReceiver.cs b/src/UnityMvvmToolkit.SourceGenerators/SyntaxReceivers/ViewsReceiver.cs new file mode 100644 index 0000000..f565f26 --- /dev/null +++ b/src/UnityMvvmToolkit.SourceGenerators/SyntaxReceivers/ViewsReceiver.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using UnityMvvmToolkit.SourceGenerators.Captures; +using UnityMvvmToolkit.SourceGenerators.Extensions; + +namespace UnityMvvmToolkit.SourceGenerators.SyntaxReceivers; + +public class ViewsReceiver : ISyntaxReceiver +{ + private const string AttributeName = "VisualTreeAsset"; + + private readonly List _captures = new(); + + public IEnumerable Captures => _captures; + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is not AttributeSyntax + { + Name: IdentifierNameSyntax { Identifier.Text: AttributeName } + } attribute) + { + return; + } + + if (attribute.ArgumentList?.Arguments.Single().Expression is not LiteralExpressionSyntax + literalExpressionSyntax) + { + return; + } + + var @class = attribute.GetParent(); + + var genericNameSyntax = @class.BaseList?.Types.Single(type => type is SimpleBaseTypeSyntax).Type as GenericNameSyntax; + if (genericNameSyntax?.TypeArgumentList.Arguments.Single() is not IdentifierNameSyntax identifierNameSyntax) + { + return; + } + + var assetPath = literalExpressionSyntax.Token.ValueText; + var viewModelIdentifier = identifierNameSyntax.Identifier.Text; + + _captures.Add(new ViewCapture(assetPath, viewModelIdentifier, @class)); + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.SourceGenerators/UnityMvvmToolkit.SourceGenerators.csproj b/src/UnityMvvmToolkit.SourceGenerators/UnityMvvmToolkit.SourceGenerators.csproj index c7cfa7c..6ab64e3 100644 --- a/src/UnityMvvmToolkit.SourceGenerators/UnityMvvmToolkit.SourceGenerators.csproj +++ b/src/UnityMvvmToolkit.SourceGenerators/UnityMvvmToolkit.SourceGenerators.csproj @@ -3,6 +3,7 @@ netstandard2.0 true + 10 @@ -14,7 +15,7 @@ - $(ProjectDir)..\UnityMvvmToolkit.UnityPackage\Assets\Plugins\UnityMvvmToolkit\Runtime\ + $(ProjectDir)..\..\samples\Unity.Mvvm.MainMenu\Assets\UnityMvvmToolkit\ diff --git a/src/UnityMvvmToolkit.SourceGenerators/ViewBindingsGenerator.cs b/src/UnityMvvmToolkit.SourceGenerators/ViewBindingsGenerator.cs new file mode 100644 index 0000000..523b92f --- /dev/null +++ b/src/UnityMvvmToolkit.SourceGenerators/ViewBindingsGenerator.cs @@ -0,0 +1,314 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using UnityMvvmToolkit.SourceGenerators.Captures; +using UnityMvvmToolkit.SourceGenerators.Extensions; +using UnityMvvmToolkit.SourceGenerators.Models; +using UnityMvvmToolkit.SourceGenerators.SyntaxReceivers; + +namespace UnityMvvmToolkit.SourceGenerators; + +[Generator] +public class ViewBindingsGenerator : ISourceGenerator +{ + private const string AssetsFolderName = "Assets"; + private const string BindableIdentifier = "Bindable"; + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new ViewBindingsReceiver()); + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.SyntaxReceiver is not ViewBindingsReceiver receiver) + { + return; + } + + foreach (var view in receiver.Views.Captures) + { + var assetFullPath = GetVisualTreeAssetPath(view); + if (File.Exists(assetFullPath) == false) + { + context.ReportDiagnostic( + Diagnostic.Create( + "LG01", + "View bindings generator", + "Visual tree asset file is not found.", + defaultSeverity: DiagnosticSeverity.Error, + severity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + warningLevel: 0)); + + return; + } + + var bindingClasses = new Dictionary(); + var visualTreeElements = GetBindableElements(assetFullPath); + + var index = 0; + foreach (var visualTreeElement in visualTreeElements) + { + if (receiver.BindableElements.Captures.TryGetValue(visualTreeElement.ClassIdentifier, + out var visualElement) == false) + { + continue; + } + + var classIdentifier = visualElement.ClassIdentifier; + var classIdentifierIndexed = $"{classIdentifier}{index}"; + var classBody = + CreateElementBindingsClass(classIdentifierIndexed, view.ViewModelIdentifier, visualElement, + visualTreeElement); + + if (classBody == null) + { + continue; + } + + index++; + bindingClasses.Add(classIdentifier, (classIdentifierIndexed, classBody)); + } + + if (bindingClasses.Count != 0) + { + context.AddSource($"{view.Class.Identifier.Text}.g.cs", GenerateView(context, view, bindingClasses)); + } + } + } + + private string CreateElementBindingsClass(string classIdentifier, string viewModelIdentifier, + BindableElementCapture visualElement, VisualTreeElementInfo visualTreeElement) + { + var valueAssignments = GetUpdateValueMethodBody(visualElement.Properties, visualTreeElement); + if (valueAssignments.Count == 0) + { + return null; + } + + return $@" + private class {classIdentifier}Bindings : IVisualElementBindings + {{ + private readonly {viewModelIdentifier} _viewModel; + private readonly {visualElement.ClassIdentifier} _visualElement; + + public {classIdentifier}Bindings({viewModelIdentifier} viewModel, {visualElement.ClassIdentifier} visualElement) + {{ + _viewModel = viewModel; + _visualElement = visualElement; + }} + + public void UpdateValues() + {{ + {string.Join(Environment.NewLine, valueAssignments)} + }} + }}"; + } + + private List GetUpdateValueMethodBody(IReadOnlyDictionary visualElementProperties, + VisualTreeElementInfo visualTreeElement) + { + var valueAssignments = new List(); + + foreach (var visualTreeAttribute in visualTreeElement.Attributes) + { + var attributeName = visualTreeAttribute.Key.Replace("-", ""); + if (visualElementProperties.TryGetValue(attributeName, out var targetPropertyName) == false) + { + continue; + } + + valueAssignments.Add($@"if (_visualElement.{targetPropertyName} != _viewModel.{visualTreeAttribute.Value}) + {{ + _visualElement.{targetPropertyName} = _viewModel.{visualTreeAttribute.Value}; + }}"); + } + + return valueAssignments; + } + + private string GenerateView(GeneratorExecutionContext context, ViewCapture view, + Dictionary bindingClasses) + { + var usingDirectives = view.Class.SyntaxTree.GetRoot().DescendantNodes() + .OfType(); + var usingDirectivesAsText = string.Join(Environment.NewLine, usingDirectives); + + // TODO: Filter using directives. + + var switchStringBuilder = new StringBuilder(); + var classesStringBuilder = new StringBuilder(); + + foreach (var @class in bindingClasses) + { + switchStringBuilder.Append($"{@class.Key} visualElement => new {@class.Value.Item1}Bindings(bindingContext, visualElement),"); + switchStringBuilder.Append($"{Environment.NewLine}\t\t\t\t"); + + classesStringBuilder.Append(@class.Value.Item2); + classesStringBuilder.Append($"{Environment.NewLine}"); + } + + return $@"using System; +using System.Runtime.CompilerServices; +using UnityEngine.UIElements; +using UnityMvvmToolkit.Common.Interfaces; +using UnityMvvmToolkit.UI.BindableVisualElements; + +{usingDirectivesAsText} + +namespace {view.Class.GetParent().Name} +{{ + public partial class {view.Class.Identifier.Text} + {{ + protected override IVisualElementBindings GetVisualElementsBindings(MainMenuViewModel bindingContext, + IBindableVisualElement bindableElement) + {{ + return bindableElement switch + {{ + {switchStringBuilder.ToString().TrimEnd()} + _ => default + }}; + }} + {classesStringBuilder.ToString().TrimEnd()} + }} +}}"; + } + + private IEnumerable GetBindableElements(string assetFullPath) + { + var result = new List(); + + var xmlDocument = new XmlDocument(); + xmlDocument.Load(assetFullPath); + + if (xmlDocument.DocumentElement == null) + { + return result; + } + + foreach (XmlNode node in xmlDocument.DocumentElement.ChildNodes) + { + if (node.Name.Contains(BindableIdentifier) == false) + { + continue; + } + + if (node.Attributes == null) + { + continue; + } + + var bindableElementInfo = new VisualTreeElementInfo(node.Name.Split('.').Last()); + + foreach (XmlAttribute attribute in node.Attributes) + { + bindableElementInfo.Attributes.Add(new KeyValuePair(attribute.Name, attribute.Value)); + } + + result.Add(bindableElementInfo); + } + + return result; + } + + private string GetVisualTreeAssetPath(ViewCapture view) + { + if (view.Class.SyntaxTree.HasCompilationUnitRoot == false) + { + return null; + } + + var viewDirectory = Path.GetDirectoryName(view.Class.SyntaxTree.FilePath); + var assetsFolderIndex = viewDirectory?.IndexOf(AssetsFolderName, StringComparison.Ordinal); + + return assetsFolderIndex is null or -1 + ? null + : Path.Combine(viewDirectory.Substring(0, assetsFolderIndex.Value + AssetsFolderName.Length), + view.AssetPath); + } + + private string GenerateBody() + { + return + $@"protected override IVisualElementBindings GetVisualElementsBindings(MainMenuViewModel bindingContext, + IBindableVisualElement bindableElement) + {{ + return bindableElement switch + {{ + BindableLabel bindableLabel => new LabelBindings(bindingContext, bindableLabel), + BindableTextField bindableTextField => new TextFieldBindings(bindingContext, bindableTextField), + _ => default + }}; + }} + + private class LabelBindings : IVisualElementBindings + {{ + private readonly MainMenuViewModel _viewModel; + private readonly BindableLabel _visualElement; + + public LabelBindings(MainMenuViewModel viewModel, BindableLabel visualElement) + {{ + _viewModel = viewModel; + _visualElement = visualElement; + }} + + public void UpdateValues() + {{ + UpdateStrValue(); + }} + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateStrValue() + {{ + if (_visualElement.text != _viewModel.StrValue) + {{ + _visualElement.text = _viewModel.StrValue; + }} + }} + }} + + private class TextFieldBindings : IVisualElementBindings, IDisposable + {{ + private readonly MainMenuViewModel _viewModel; + private readonly BindableTextField _visualElement; + + public TextFieldBindings(MainMenuViewModel viewModel, BindableTextField visualElement) + {{ + _viewModel = viewModel; + + _visualElement = visualElement; + _visualElement.RegisterValueChangedCallback(OnVisualElementValueChanged); + }} + + public void Dispose() + {{ + _visualElement.UnregisterValueChangedCallback(OnVisualElementValueChanged); + }} + + public void UpdateValues() + {{ + UpdateStrValue(); + }} + + private void OnVisualElementValueChanged(ChangeEvent e) + {{ + _viewModel.StrValue = e.newValue; + }} + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateStrValue() + {{ + if (_visualElement.value != _viewModel.StrValue) + {{ + _visualElement.SetValueWithoutNotify(_viewModel.StrValue); + }} + }} + }}"; + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Attributes/VisualTreeAssetAttribute.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Attributes/VisualTreeAssetAttribute.cs new file mode 100644 index 0000000..0bf1098 --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Attributes/VisualTreeAssetAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace UnityMvvmToolkit.Common.Attributes +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class VisualTreeAssetAttribute : Attribute + { + public VisualTreeAssetAttribute(string assetPath) + { + AssetPath = assetPath; + } + + public string AssetPath { get; } + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Attributes/VisualTreeAssetAttribute.cs.meta b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Attributes/VisualTreeAssetAttribute.cs.meta new file mode 100644 index 0000000..b2e1125 --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Attributes/VisualTreeAssetAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d4e3531fa92d4d629ad073fb07c9f224 +timeCreated: 1657809287 \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Interfaces/IView.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Interfaces/IView.cs new file mode 100644 index 0000000..a5abded --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Interfaces/IView.cs @@ -0,0 +1,6 @@ +namespace UnityMvvmToolkit.Common.Interfaces +{ + public interface IView + { + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Interfaces/IView.cs.meta b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Interfaces/IView.cs.meta new file mode 100644 index 0000000..30b6054 --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Interfaces/IView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e5a9b259942b63e47ac312d34bfe9e1d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Interfaces/IVisualElementBindings.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Interfaces/IVisualElementBindings.cs new file mode 100644 index 0000000..69abbf1 --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Interfaces/IVisualElementBindings.cs @@ -0,0 +1,7 @@ +namespace UnityMvvmToolkit.Common.Interfaces +{ + public interface IVisualElementBindings + { + void UpdateValues(); + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Interfaces/IVisualElementBindings.cs.meta b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Interfaces/IVisualElementBindings.cs.meta new file mode 100644 index 0000000..fe8d7c1 --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/Interfaces/IVisualElementBindings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6084ace45cd4edd4e833e57d9758ab6d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/ViewModel.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/ViewModel.cs new file mode 100644 index 0000000..f875b65 --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/ViewModel.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace UnityMvvmToolkit.Common +{ + public abstract class ViewModel : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected bool Set(ref T oldValue, T newValue, [CallerMemberName] string propertyName = default) + { + if (EqualityComparer.Default.Equals(oldValue, newValue)) + { + return false; + } + + oldValue = newValue; + OnPropertyChanged(propertyName); + + return true; + } + + protected bool Set(T oldValue, T newValue, TModel model, Action callback, + [CallerMemberName] string propertyName = default) where TModel : class + { + if (EqualityComparer.Default.Equals(oldValue, newValue)) + { + return false; + } + + callback(model, newValue); + OnPropertyChanged(propertyName); + + return true; + } + + protected void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/ViewModel.cs.meta b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/ViewModel.cs.meta new file mode 100644 index 0000000..419b00e --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.Common/ViewModel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9df4a38140ed43b4eb36e8bab44d7f27 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.SourceGenerators.dll b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.SourceGenerators.dll index 347cef2..65219b9 100644 Binary files a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.SourceGenerators.dll and b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.SourceGenerators.dll differ diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.UI/BindableVisualElements/BindableLabel.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.UI/BindableVisualElements/BindableLabel.cs new file mode 100644 index 0000000..2aa7581 --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.UI/BindableVisualElements/BindableLabel.cs @@ -0,0 +1,29 @@ +using UnityEngine.UIElements; +using UnityMvvmToolkit.Common.Attributes; +using UnityMvvmToolkit.Common.Interfaces; + +namespace UnityMvvmToolkit.UI.BindableVisualElements +{ + public class BindableLabel : Label, IBindableVisualElement + { + [BindTo(nameof(text))] + public string BindingTextPath { get; set; } + + public new class UxmlFactory : UxmlFactory + { + } + + public new class UxmlTraits : Label.UxmlTraits + { + private readonly UxmlStringAttributeDescription _bindingTextAttribute = new() + { name = "binding-text-path", defaultValue = "binding-property-name" }; + + public override void Init(VisualElement visualElement, IUxmlAttributes bag, CreationContext context) + { + base.Init(visualElement, bag, context); + ((BindableLabel) visualElement).BindingTextPath = + _bindingTextAttribute.GetValueFromBag(bag, context); + } + } + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.UI/BindableVisualElements/BindableLabel.cs.meta b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.UI/BindableVisualElements/BindableLabel.cs.meta new file mode 100644 index 0000000..036cc05 --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.UI/BindableVisualElements/BindableLabel.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dd55009627c7475ba7f9a364c7292a5b +timeCreated: 1657789669 \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.UI/View.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.UI/View.cs new file mode 100644 index 0000000..63be022 --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.UI/View.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using UnityEngine; +using UnityEngine.UIElements; +using UnityMvvmToolkit.Common.Interfaces; + +namespace UnityMvvmToolkit.UI +{ + [RequireComponent(typeof(UIDocument))] + public abstract class View : MonoBehaviour where TBindingContext : class, INotifyPropertyChanged, new() + { + private UIDocument _uiDocument; + private TBindingContext _bindingContext; + private List _disposables; + private Dictionary> _visualElementsBindings; + + private void Awake() + { + _uiDocument = GetComponent(); + _bindingContext = GetBindingContext(); + + _disposables = new List(); + _visualElementsBindings = new Dictionary>(); + + BindElements(_bindingContext, _uiDocument.rootVisualElement); + } + + private void OnEnable() + { + _bindingContext.PropertyChanged += OnBindingContextPropertyChanged; + } + + private void OnDisable() + { + _bindingContext.PropertyChanged -= OnBindingContextPropertyChanged; + } + + private void OnDestroy() + { + // TODO: Unregister bindable elements (like TextField). + + foreach (var disposable in _disposables) + { + disposable.Dispose(); + } + } + + protected virtual TBindingContext GetBindingContext() + { + return new TBindingContext(); // TODO: Change DataContext dynamically? + } + + protected virtual IVisualElementBindings GetVisualElementsBindings(TBindingContext bindingContext, + IBindableVisualElement bindableElement) + { + throw new NotImplementedException(); + } + + private void BindElements(TBindingContext bindingContext, VisualElement rootVisualElement) + { + // var bindingContextProperties = + // typeof(TBindingContext).GetProperties(BindingFlags.Public | BindingFlags.Instance | + // BindingFlags.DeclaredOnly); + + rootVisualElement.Query().ForEach(visualElement => + { + if (visualElement is IBindableVisualElement bindableElement) + { + RegisterBindableElement(bindableElement); + } + }); + } + + private void RegisterBindableElement(IBindableVisualElement bindableElement) + { + var bindingProperties = bindableElement.GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + + foreach (var bindingPropertyInfo in bindingProperties) + { + var sourcePropertyName = bindingPropertyInfo.GetValue(bindableElement).ToString(); + if (string.IsNullOrWhiteSpace(sourcePropertyName)) + { + continue; + } + + var sourcePropertyInfo = typeof(TBindingContext).GetProperty(sourcePropertyName); // TODO: Cache properties to dictionary. + if (sourcePropertyInfo == null) + { + throw new NullReferenceException(nameof(sourcePropertyInfo)); + } + + var visualElementBindings = GetVisualElementsBindings(_bindingContext, bindableElement); + if (visualElementBindings == null) + { + throw new NullReferenceException(nameof(visualElementBindings)); + } + + if (visualElementBindings is IDisposable disposable) + { + _disposables.Add(disposable); + } + + if (_visualElementsBindings.TryGetValue(sourcePropertyName, out var visualElements) == false) + { + visualElements = new HashSet(); + _visualElementsBindings.Add(sourcePropertyName, visualElements); + } + + visualElementBindings.UpdateValues(); + visualElements.Add(visualElementBindings); + } + } + private void OnBindingContextPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (_visualElementsBindings.TryGetValue(e.PropertyName, out var visualElements)) + { + foreach (var visualElement in visualElements) + { + visualElement.UpdateValues(); + } + } + } + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.UI/View.cs.meta b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.UI/View.cs.meta new file mode 100644 index 0000000..0f5d939 --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UnityMvvmToolkit.UI/View.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0c23133e65c84117a6d710966201599b +timeCreated: 1657793186 \ No newline at end of file