diff --git a/README.md b/README.md index ac16635..355a3ed 100644 --- a/README.md +++ b/README.md @@ -770,6 +770,7 @@ The included UI elements are: - [BindableLabel](#bindablelabel) - [BindableTextField](#bindabletextfield) - [BindableButton](#bindablebutton) +- [BindableDropdownField](#bindabledropdownfield) - [BindableListView](#bindablelistview) - [BindableScrollView](#bindablescrollview) - [BindingContextProvider](#bindingcontextprovider) @@ -840,6 +841,37 @@ The `BindableButton` can be bound to the following commands: To pass a parameter to the viewmodel, see the [ParameterValueConverter](#parametervalueconverterttargettype) section. +#### BindableDropdownField + +The `BindableDropdownField` allows the user to pick a choice from a list of options. The `BindingSelectedItemPath` attribute is optional. + +```csharp +public class DropdownFieldViewModel : IBindingContext +{ + public DropdownFieldViewModel() + { + var items = new ObservableCollection + { + "Value 1", + "Value 2", + "Value 3" + }; + + Items = new ReadOnlyProperty>(items); + SelectedItem = new Property("Value 1"); + } + + public IReadOnlyProperty> Items { get; } + public IProperty SelectedItem { get; } +} +``` + +```xml + + + +``` + #### BindableListView The `BindableListView` control is the most efficient way to create lists. It uses virtualization and creates VisualElements only for visible items. Use the `binding-items-source-path` of the `BindableListView` to bind to an `ObservableCollection`. diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Common/BindableElementData.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Common/BindableElementData.cs new file mode 100644 index 0000000..982609b --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Common/BindableElementData.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using UnityMvvmToolkit.Core.Interfaces; + +namespace UnityMvvmToolkit.Common +{ + public sealed class BindableElementData : IDisposable + { + private readonly List _bindableElements; + + public BindableElementData(List bindableElements) + { + _bindableElements = bindableElements; + } + + public IBindingContext BindingContext { get; set; } + public IReadOnlyList BindableElements => _bindableElements; + + public void Dispose() + { + _bindableElements.Clear(); + BindingContext = default; + } + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Common/BindableElementData.cs.meta b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Common/BindableElementData.cs.meta new file mode 100644 index 0000000..fe58f86 --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Common/BindableElementData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0338651671e94fb7b83932e9aed12fc4 +timeCreated: 1687771988 \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UGUI/BindableUGUIElements/BindableDropdown.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UGUI/BindableUGUIElements/BindableDropdown.cs new file mode 100644 index 0000000..3f0da78 --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UGUI/BindableUGUIElements/BindableDropdown.cs @@ -0,0 +1,127 @@ +#if UNITYMVVMTOOLKIT_TEXTMESHPRO_SUPPORT + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using System.Runtime.CompilerServices; +using TMPro; +using UnityEngine; +using UnityMvvmToolkit.Core; +using UnityMvvmToolkit.Core.Extensions; +using UnityMvvmToolkit.Core.Interfaces; + +namespace UnityMvvmToolkit.UGUI.BindableUGUIElements +{ + [RequireComponent(typeof(TMP_Dropdown))] + public class BindableDropdown : MonoBehaviour, IBindableElement + { + [SerializeField] private TMP_Dropdown _dropdown; + [SerializeField] private string _bindingSelectedItemPath; + [SerializeField] private string _bindingItemsSourcePath; + + private IProperty _selectedItemProperty; + private IReadOnlyProperty> _itemsSource; + + private PropertyBindingData _selectedItemBindingData; + private PropertyBindingData _itemsSourceBindingData; + + public void SetBindingContext(IBindingContext context, IObjectProvider objectProvider) + { + if (string.IsNullOrWhiteSpace(_bindingItemsSourcePath) == false) + { + _itemsSourceBindingData ??= _bindingItemsSourcePath.ToPropertyBindingData(); + _itemsSource = objectProvider + .RentReadOnlyProperty>(context, _itemsSourceBindingData); + _itemsSource.Value.CollectionChanged += OnItemsCollectionChanged; + _dropdown.options = new List(_itemsSource.Value.Select(value => new TMP_Dropdown.OptionData(value))); + } + + if (string.IsNullOrWhiteSpace(_bindingSelectedItemPath) == false) + { + _selectedItemBindingData ??= _bindingSelectedItemPath.ToPropertyBindingData(); + _selectedItemProperty = objectProvider.RentProperty(context, _selectedItemBindingData); + _selectedItemProperty.ValueChanged += OnPropertySelectedItemChanged; + + var foundIndex = _dropdown.options.FindIndex(option => option.text == _selectedItemProperty.Value); + if (foundIndex != -1) + { + UpdateControlValue(foundIndex); + } + + _dropdown.onValueChanged.AddListener(OnControlValueChanged); + _selectedItemProperty.Value = _dropdown.options.Count> 0 ? _dropdown.options[0].text : default; + } + } + + private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + foreach (string newItem in e.NewItems) + { + _dropdown.options.Add(new TMP_Dropdown.OptionData(newItem)); + } + } + + if (e.Action == NotifyCollectionChangedAction.Remove) + { + foreach (string oldItem in e.OldItems) + { + _dropdown.options.Remove(new TMP_Dropdown.OptionData(oldItem)); + } + } + + if (e.Action == NotifyCollectionChangedAction.Reset) + { + _dropdown.options.Clear(); + } + } + + public virtual void ResetBindingContext(IObjectProvider objectProvider) + { + if (_itemsSource != null) + { + _itemsSource.Value.CollectionChanged -= OnItemsCollectionChanged; + objectProvider.ReturnReadOnlyProperty(_itemsSource); + _itemsSource = null; + _dropdown.options = new List(); + } + + if (_selectedItemProperty != null) + { + _selectedItemProperty.ValueChanged -= OnPropertySelectedItemChanged; + objectProvider.ReturnProperty(_selectedItemProperty); + _selectedItemProperty = null; + _dropdown.onValueChanged.RemoveListener(OnControlValueChanged); + } + + UpdateControlValue(default); + } + + protected virtual void OnControlValueChanged(int index) + { + _selectedItemProperty.Value = _dropdown.options[index].text; + } + + private void OnPropertySelectedItemChanged(object sender, string newValue) + { + var foundIndex = _dropdown.options.FindIndex(option => option.text == newValue); + if (foundIndex == -1) + { + return; + } + + UpdateControlValue(foundIndex); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual void UpdateControlValue(int newValue) + { + _dropdown.SetValueWithoutNotify(newValue); + } + } +} + +#endif \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UGUI/BindableUGUIElements/BindableDropdown.cs.meta b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UGUI/BindableUGUIElements/BindableDropdown.cs.meta new file mode 100644 index 0000000..05c1cb9 --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UGUI/BindableUGUIElements/BindableDropdown.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7044586e66117e48948637bf2c66eaa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableButton.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableButton.cs index a303a4c..69702a8 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableButton.cs +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableButton.cs @@ -34,7 +34,7 @@ public virtual void SetBindingContext(IBindingContext context, IObjectProvider o public virtual void ResetBindingContext(IObjectProvider objectProvider) { - if (_command == null) + if (_command is null) { return; } diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableDropdownField.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableDropdownField.cs new file mode 100644 index 0000000..ef40e6d --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableDropdownField.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using System.Runtime.CompilerServices; +using UnityEngine.UIElements; +using UnityMvvmToolkit.Common.Interfaces; +using UnityMvvmToolkit.Core; +using UnityMvvmToolkit.Core.Extensions; +using UnityMvvmToolkit.Core.Interfaces; + +namespace UnityMvvmToolkit.UITK.BindableUIElements +{ + public partial class BindableDropdownField : DropdownField, IBindableCollection, IInitializable, IDisposable + { + private IProperty _selectedItemProperty; + private PropertyBindingData _selectedItemBindingData; + + private PropertyBindingData _itemsSourceBindingData; + private IReadOnlyProperty> _itemsSource; + + public void Initialize() + { + choices = new List(); + } + + public void Dispose() + { + RemoveAllItems(); + } + + public void SetBindingContext(IBindingContext context, IObjectProvider objectProvider) + { + if (string.IsNullOrWhiteSpace(BindingItemsSourcePath)) + { + return; + } + + _itemsSourceBindingData ??= BindingItemsSourcePath.ToPropertyBindingData(); + + _itemsSource = + objectProvider.RentReadOnlyProperty>(context, _itemsSourceBindingData); + _itemsSource.Value.CollectionChanged += OnItemsCollectionChanged; + + AddItems(_itemsSource.Value); + + if (string.IsNullOrWhiteSpace(BindingSelectedItemPath) == false) + { + _selectedItemBindingData ??= BindingSelectedItemPath.ToPropertyBindingData(); + + _selectedItemProperty = objectProvider.RentProperty(context, _selectedItemBindingData); + + if (string.IsNullOrWhiteSpace(_selectedItemProperty.Value)) + { + _selectedItemProperty.Value = choices.Count> 0 ? choices[0] : default; + } + else + { + UpdateControlValue(_selectedItemProperty.Value); + } + + _selectedItemProperty.ValueChanged += OnSelectedItemValueChanged; + this.RegisterValueChangedCallback(OnControlSelectedValueChanged); + } + } + + public virtual void ResetBindingContext(IObjectProvider objectProvider) + { + if (_itemsSource is null) + { + return; + } + + _itemsSource.Value.CollectionChanged -= OnItemsCollectionChanged; + objectProvider.ReturnReadOnlyProperty(_itemsSource); + _itemsSource = null; + + if (_selectedItemProperty is not null) + { + _selectedItemProperty.ValueChanged -= OnSelectedItemValueChanged; + objectProvider.ReturnProperty(_selectedItemProperty); + _selectedItemProperty = null; + + this.UnregisterValueChangedCallback(OnControlSelectedValueChanged); + } + + RemoveAllItems(); + } + + protected virtual void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + foreach (string newItem in e.NewItems) + { + AddItem(newItem); + } + } + + if (e.Action == NotifyCollectionChangedAction.Remove) + { + foreach (string oldItem in e.OldItems) + { + RemoveItem(oldItem); + } + } + + if (e.Action == NotifyCollectionChangedAction.Reset) + { + if (_itemsSource.Value.Count == 0) + { + RemoveAllItems(); + } + else + { + throw new InvalidOperationException("Action not supported."); + } + } + } + + protected virtual void OnControlSelectedValueChanged(ChangeEvent e) + { + _selectedItemProperty.Value = e.newValue; + } + + private void OnSelectedItemValueChanged(object sender, string newValue) + { + UpdateControlValue(newValue); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual void UpdateControlValue(string selectedItem) + { + if (choices.Any() && choices.Contains(selectedItem) == false) + { + throw new InvalidOperationException($"\"{selectedItem}\" is not presented in the collection."); + } + + SetValueWithoutNotify(selectedItem); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddItems(IEnumerable items) + { + foreach (var item in items) + { + AddItem(item); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddItem(string item) + { + if (string.IsNullOrWhiteSpace(item)) + { + throw new NullReferenceException("Item cannot be null or empty."); + } + + choices.Add(item); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RemoveItem(string item) + { + choices.Remove(item); + + if (value == item) + { + value = choices.Count == 0 ? default : choices[^1]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RemoveAllItems() + { + choices.Clear(); + value = default; + } + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableDropdownField.cs.meta b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableDropdownField.cs.meta new file mode 100644 index 0000000..065981e --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableDropdownField.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8c46923c0a364c539ce1f2895dec04ed +timeCreated: 1686411067 \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableLabel.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableLabel.cs index 4fa9dcf..ddb736d 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableLabel.cs +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableLabel.cs @@ -28,7 +28,7 @@ public virtual void SetBindingContext(IBindingContext context, IObjectProvider o public virtual void ResetBindingContext(IObjectProvider objectProvider) { - if (_textProperty == null) + if (_textProperty is null) { return; } diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableListView.T.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableListView.T.cs index fb62d52..e610974 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableListView.T.cs +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableListView.T.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; -using JetBrains.Annotations; using UnityEngine.UIElements; using UnityMvvmToolkit.Common.Interfaces; using UnityMvvmToolkit.Core; @@ -18,14 +17,17 @@ public abstract partial class BindableListView : ListView, private VisualTreeAsset _itemTemplate; private PropertyBindingData _itemsSourceBindingData; - private IReadOnlyProperty> _itemsSource; + private ObservableCollection _itemsSource; + private IReadOnlyProperty> _itemsSourceProperty; private IObjectProvider _objectProvider; private List _itemAssets; + private Dictionary _activeItems; public virtual void Initialize() { _itemAssets = new List(); + _activeItems = new Dictionary(); } public virtual void Dispose() @@ -36,20 +38,27 @@ public virtual void Dispose() } _itemAssets.Clear(); + _activeItems.Clear(); } public virtual void SetBindingContext(IBindingContext context, IObjectProvider objectProvider) { + if (string.IsNullOrWhiteSpace(BindingItemsSourcePath)) + { + return; + } + _itemsSourceBindingData ??= BindingItemsSourcePath.ToPropertyBindingData(); _itemTemplate ??= objectProvider.GetCollectionItemTemplate(); _objectProvider = objectProvider; - _itemsSource = objectProvider + _itemsSourceProperty = objectProvider .RentReadOnlyProperty>(context, _itemsSourceBindingData); - _itemsSource.Value.CollectionChanged += OnItemsCollectionChanged; + _itemsSource = _itemsSourceProperty.Value; + _itemsSource.CollectionChanged += OnItemsCollectionChanged; - itemsSource = _itemsSource.Value; + itemsSource = _itemsSource; makeItem += OnMakeItem; bindItem += OnBindItem; unbindItem += OnUnbindItem; @@ -57,23 +66,25 @@ public virtual void SetBindingContext(IBindingContext context, IObjectProvider o public virtual void ResetBindingContext(IObjectProvider objectProvider) { - if (_itemsSource is null) + if (_itemsSourceProperty is null) { return; } - _itemsSource.Value.CollectionChanged -= OnItemsCollectionChanged; + _itemsSource.CollectionChanged -= OnItemsCollectionChanged; makeItem -= OnMakeItem; bindItem -= OnBindItem; unbindItem -= OnUnbindItem; itemsSource = Array.Empty(); - objectProvider.ReturnReadOnlyProperty(_itemsSource); + objectProvider.ReturnReadOnlyProperty(_itemsSourceProperty); - _itemsSource = null; _itemTemplate = null; _objectProvider = null; + + _itemsSource = null; + _itemsSourceProperty = null; } protected virtual VisualElement MakeItem(VisualTreeAsset itemTemplate) @@ -89,20 +100,28 @@ protected virtual void BindItem(VisualElement item, int index, TItemBindingConte item.SetChildsBindingContext(bindingContext, objectProvider); } - protected virtual void UnbindItem(VisualElement item, int index, [CanBeNull] TItemBindingContext bindingContext, + protected virtual void UnbindItem(VisualElement item, int index, TItemBindingContext bindingContext, IObjectProvider objectProvider) { item.ResetChildsBindingContext(objectProvider); } - private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + protected virtual void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - RefreshItems(); // TODO: Do not refresh all items. +#if UNITY_2021 + if (e.Action is NotifyCollectionChangedAction.Remove or NotifyCollectionChangedAction.Reset) + { + Rebuild(); + return; + } +#endif + RefreshItems(); } private VisualElement OnMakeItem() { var item = MakeItem(_itemTemplate); + _itemAssets.Add(item); return item; @@ -110,18 +129,21 @@ private VisualElement OnMakeItem() private void OnBindItem(VisualElement item, int index) { - BindItem(item, index, _itemsSource.Value[index], _objectProvider); + var itemId = item.GetHashCode(); + var itemBindingContext = _itemsSource[index]; + + _activeItems.Add(itemId, itemBindingContext); + + BindItem(item, index, itemBindingContext, _objectProvider); } private void OnUnbindItem(VisualElement item, int index) { - if (index>= 0 && index < itemsSource.Count) - { - UnbindItem(item, index, _itemsSource.Value[index], _objectProvider); - } - else + var itemId = item.GetHashCode(); + + if (_activeItems.Remove(itemId, out var itemBindingContext)) { - UnbindItem(item, index, default, _objectProvider); + UnbindItem(item, index, itemBindingContext, _objectProvider); } } } diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableScrollView.T.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableScrollView.T.cs index 9279c96..e17ca43 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableScrollView.T.cs +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableScrollView.T.cs @@ -21,7 +21,8 @@ public abstract partial class BindableScrollView : ScrollVi private VisualTreeAsset _itemTemplate; private PropertyBindingData _itemsSourceBindingData; - private IReadOnlyProperty> _itemsSource; + private ObservableCollection _itemsSource; + private IReadOnlyProperty> _itemsSourceProperty; private IObjectProvider _objectProvider; @@ -44,7 +45,7 @@ public virtual void Dispose() } else { - ClearItems(_itemsSource.Value); + ClearItems(_itemsSource); } _itemAssetsPool.Dispose(); @@ -52,34 +53,42 @@ public virtual void Dispose() public virtual void SetBindingContext(IBindingContext context, IObjectProvider objectProvider) { + if (string.IsNullOrWhiteSpace(BindingItemsSourcePath)) + { + return; + } + _itemsSourceBindingData ??= BindingItemsSourcePath.ToPropertyBindingData(); _itemTemplate ??= objectProvider.GetCollectionItemTemplate(); _objectProvider = objectProvider; - _itemsSource = objectProvider + _itemsSourceProperty = objectProvider .RentReadOnlyProperty>(context, _itemsSourceBindingData); - _itemsSource.Value.CollectionChanged += OnItemsCollectionChanged; + _itemsSource = _itemsSourceProperty.Value; + _itemsSource.CollectionChanged += OnItemsCollectionChanged; - AddItems(_itemsSource.Value); + AddItems(_itemsSource); } public virtual void ResetBindingContext(IObjectProvider objectProvider) { - if (_itemsSource is null) + if (_itemsSourceProperty is null) { return; } - _itemsSource.Value.CollectionChanged -= OnItemsCollectionChanged; + _itemsSource.CollectionChanged -= OnItemsCollectionChanged; - ClearItems(_itemsSource.Value); + ClearItems(_itemsSource); - objectProvider.ReturnReadOnlyProperty(_itemsSource); + objectProvider.ReturnReadOnlyProperty(_itemsSourceProperty); - _itemsSource = null; _itemTemplate = null; _objectProvider = null; + + _itemsSource = null; + _itemsSourceProperty = null; } protected virtual VisualElement MakeItem(VisualTreeAsset itemTemplate) @@ -101,7 +110,7 @@ protected virtual void UnbindItem(VisualElement item, int index, TItemBindingCon item.ResetChildsBindingContext(objectProvider); } - private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + protected virtual void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add) { @@ -120,6 +129,18 @@ private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEven RemoveItem((TItemBindingContext) oldItem); } } + + if (e.Action == NotifyCollectionChangedAction.Reset) + { + if (_itemsSource.Count == 0) + { + ClearItems(); + } + else + { + throw new InvalidOperationException("Action not supported."); + } + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -168,6 +189,24 @@ private void ClearItems(IReadOnlyList items) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ClearItems() + { + foreach (var (_, item) in _itemAssets) + { + _itemsCount--; + + if (_objectProvider is not null) + { + UnbindItem(item, _itemsCount, item.GetBindingContext(), _objectProvider); + } + + _itemAssetsPool.Release(item); + } + + _itemAssets.Clear(); + } + private VisualElement OnPoolInstantiateItem() { return MakeItem(_itemTemplate); diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableTextField.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableTextField.cs index 9ee08b7..c7a7bca 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableTextField.cs +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableTextField.cs @@ -29,7 +29,7 @@ public virtual void SetBindingContext(IBindingContext context, IObjectProvider o public virtual void ResetBindingContext(IObjectProvider objectProvider) { - if (_valueProperty == null) + if (_valueProperty is null) { return; } diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableDropdownField.Uxml.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableDropdownField.Uxml.cs new file mode 100644 index 0000000..ca8ad5d --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableDropdownField.Uxml.cs @@ -0,0 +1,55 @@ +using UnityEngine.UIElements; +using UnityMvvmToolkit.UITK.Extensions; + +namespace UnityMvvmToolkit.UITK.BindableUIElements +{ + partial class BindableDropdownField + { + public string BindingItemsSourcePath { get; private set; } + public string BindingSelectedItemPath { get; private set; } + + public new class UxmlFactory : UxmlFactory + { + } + +#if UNITY_2023_2_OR_NEWER + [System.Serializable] + public new class UxmlSerializedData : DropdownField.UxmlSerializedData + { + // ReSharper disable once InconsistentNaming + #pragma warning disable 649 + [UnityEngine.SerializeField] private string BindingItemsSourcePath; + [UnityEngine.SerializeField] private string BindingSelectedItemPath; + #pragma warning restore 649 + + public override object CreateInstance() => new BindableDropdownField(); + public override void Deserialize(object visualElement) + { + base.Deserialize(visualElement); + + var bindableDropdownField = visualElement.As(); + bindableDropdownField.BindingItemsSourcePath = BindingItemsSourcePath; + bindableDropdownField.BindingSelectedItemPath = BindingSelectedItemPath; + } + } +#else + public new class UxmlTraits : DropdownField.UxmlTraits + { + private readonly UxmlStringAttributeDescription _bindingItemsSourcePath = new() + { name = "binding-items-source-path", defaultValue = "" }; + + private readonly UxmlStringAttributeDescription _bindingSelectedItemPath = new() + { name = "binding-selected-item-path", defaultValue = "" }; + + public override void Init(VisualElement visualElement, IUxmlAttributes bag, CreationContext context) + { + base.Init(visualElement, bag, context); + + var bindableDropdownField = visualElement.As(); + bindableDropdownField.BindingItemsSourcePath = _bindingItemsSourcePath.GetValueFromBag(bag, context); + bindableDropdownField.BindingSelectedItemPath = _bindingSelectedItemPath.GetValueFromBag(bag, context); + } + } +#endif + } +} \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableDropdownField.Uxml.cs.meta b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableDropdownField.Uxml.cs.meta new file mode 100644 index 0000000..32d3949 --- /dev/null +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableDropdownField.Uxml.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6c58f7387a96411e88df2f3f4963bb93 +timeCreated: 1686411216 \ No newline at end of file diff --git a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/Extensions/VisualElementExtensions.cs b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/Extensions/VisualElementExtensions.cs index 27f84fa..5a4992b 100644 --- a/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/Extensions/VisualElementExtensions.cs +++ b/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/Extensions/VisualElementExtensions.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using JetBrains.Annotations; using UnityEngine.UIElements; +using UnityMvvmToolkit.Common; using UnityMvvmToolkit.Common.Interfaces; using UnityMvvmToolkit.Core.Interfaces; @@ -14,7 +15,7 @@ public static partial class VisualElementExtensions public static VisualElement InstantiateBindableElement(this VisualTreeAsset visualTreeAsset) { var visualElement = visualTreeAsset.Instantiate(); - visualElement.userData = visualElement.GetBindableChilds(); + visualElement.userData = new BindableElementData(visualElement.GetBindableChilds()); return visualElement; } @@ -42,7 +43,7 @@ public static VisualElement InitializeBindableElement(this VisualElement visualE initializable.Initialize(); } - var bindableElements = (List) visualElement.userData; + var bindableElements = ((BindableElementData) visualElement.userData).BindableElements; for (var i = 0; i < bindableElements.Count; i++) { @@ -55,11 +56,21 @@ public static VisualElement InitializeBindableElement(this VisualElement visualE return visualElement; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TBindingContext GetBindingContext(this VisualElement visualElement) + where TBindingContext : IBindingContext + { + return (TBindingContext) ((BindableElementData) visualElement.userData).BindingContext; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetChildsBindingContext(this VisualElement visualElement, IBindingContext bindingContext, IObjectProvider objectProvider) { - var bindableElements = (List) visualElement.userData; + var bindableElementData = (BindableElementData) visualElement.userData; + bindableElementData.BindingContext = bindingContext; + + var bindableElements = bindableElementData.BindableElements; for (var i = 0; i < bindableElements.Count; i++) { @@ -70,7 +81,10 @@ public static void SetChildsBindingContext(this VisualElement visualElement, IBi [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ResetChildsBindingContext(this VisualElement visualElement, IObjectProvider objectProvider) { - var bindableElements = (List) visualElement.userData; + var bindableElementData = (BindableElementData) visualElement.userData; + bindableElementData.BindingContext = default; + + var bindableElements = bindableElementData.BindableElements; for (var i = 0; i < bindableElements.Count; i++) { @@ -82,7 +96,8 @@ public static void ResetChildsBindingContext(this VisualElement visualElement, I public static void DisposeBindableElement(this VisualElement visualElement, [CanBeNull] IObjectProvider objectProvider = null) { - var bindableElements = (List) visualElement.userData; + var bindableElementData = (BindableElementData) visualElement.userData; + var bindableElements = bindableElementData.BindableElements; for (var i = 0; i < bindableElements.Count; i++) { @@ -104,8 +119,8 @@ public static void DisposeBindableElement(this VisualElement visualElement, disposable.Dispose(); } - bindableElements.Clear(); - visualElement.userData = null; + bindableElementData.Dispose(); + visualElement.userData = default; } [MethodImpl(MethodImplOptions.AggressiveInlining)]

AltStyle によって変換されたページ (->オリジナル) /