From cf0688565a4c1044d5361380d4388ae883838bbd Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 5 Jun 2022 17:34:34 +0200 Subject: [PATCH 001/136] Refactor value storage to reduce overhead and memory allocations. --- src/Avalonia.Base/Avalonia.Base.csproj | 1 + src/Avalonia.Base/AvaloniaObject.cs | 16 + src/Avalonia.Base/StyledElement.cs | 18 +- .../Utilities/AvaloniaPropertyValueStore.cs | 236 +++++++----- src/Avalonia.Base/ValueStore.cs | 12 +- .../Avalonia.Benchmarks.csproj | 3 + .../Base/ValueStoreAddRemoveBenchmarks.cs | 351 ++++++++++++++++++ .../Layout/ControlsBenchmark.cs | 33 ++ 8 files changed, 561 insertions(+), 109 deletions(-) create mode 100644 tests/Avalonia.Benchmarks/Base/ValueStoreAddRemoveBenchmarks.cs diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 16a91b10d6..15f3b67643 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 1f14ddede4..240ce80825 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -25,6 +25,7 @@ namespace Avalonia private List? _inheritanceChildren; private ValueStore? _values; private bool _batchUpdate; + private bool _isInitializing; /// /// Initializes a new instance of the class. @@ -126,6 +127,9 @@ namespace Avalonia { _values = new ValueStore(this); + _values.IsInitializing = _isInitializing; + _isInitializing = false; + if (_batchUpdate) _values.BeginBatchUpdate(); } @@ -493,6 +497,18 @@ namespace Avalonia _batchUpdate = false; _values?.EndBatchUpdate(); } + + internal void SetValueStoreIsInitializing(bool isInitializing) + { + if (_values is null) + { + _isInitializing = isInitializing; + + return; + } + + _values.IsInitializing = isInitializing; + } /// internal void AddInheritanceChild(AvaloniaObject child) diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index f98d2cdbcc..b19ef85cfa 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -306,6 +306,11 @@ namespace Avalonia public virtual void BeginInit() { ++_initCount; + + if (_initCount == 1) + { + SetValueStoreIsInitializing(true); + } } /// @@ -316,10 +321,15 @@ namespace Avalonia throw new InvalidOperationException("BeginInit was not called."); } - if (--_initCount == 0 && _logicalRoot != null) + if (--_initCount == 0) { - ApplyStyling(); - InitializeIfNeeded(); + if (_logicalRoot is not null) + { + ApplyStyling(); + InitializeIfNeeded(); + } + + SetValueStoreIsInitializing(false); } } @@ -337,11 +347,13 @@ namespace Avalonia try { BeginBatchUpdate(); + SetValueStoreIsInitializing(true); AvaloniaLocator.Current.GetService()?.ApplyStyles(this); } finally { EndBatchUpdate(); + SetValueStoreIsInitializing(false); } _styled = true; diff --git a/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs b/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs index cbe3771577..9dbe0765de 100644 --- a/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs +++ b/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Avalonia.Utilities @@ -8,166 +7,197 @@ namespace Avalonia.Utilities /// Stores values with as key. /// /// Stored value type. - internal sealed class AvaloniaPropertyValueStore + internal struct AvaloniaPropertyValueStore { - // The last item in the list is always int.MaxValue. - private static readonly Entry[] s_emptyEntries = { new Entry { PropertyId = int.MaxValue, Value = default! } }; - - private Entry[] _entries; + private Entry[]? _entries; + private int _entryCount; public AvaloniaPropertyValueStore() { - _entries = s_emptyEntries; + _entries = null; + _entryCount = 0; + IsInitializing = false; + InitialSize = 4; } - public int Count => _entries.Length - 1; - public TValue this[int index] => _entries[index].Value; + public int Count => _entryCount; + + public bool IsInitializing { get; set; } + + public int InitialSize { get; set; } + + public TValue this[int index] => _entries![index].Value; - private (int, bool) TryFindEntry(int propertyId) + private EntryIndex LookupEntry(int propertyId) { - if (_entries.Length <= 12) + int checkIndex; + int iLo = 0; + int iHi = _entryCount; + + if (iHi <= 0) { - // For small lists, we use an optimized linear search. Since the last item in the list - // is always int.MaxValue, we can skip a conditional branch in each iteration. - // By unrolling the loop, we can skip another unconditional branch in each iteration. - - if (_entries[0].PropertyId >= propertyId) - return (0, _entries[0].PropertyId == propertyId); - if (_entries[1].PropertyId >= propertyId) - return (1, _entries[1].PropertyId == propertyId); - if (_entries[2].PropertyId >= propertyId) - return (2, _entries[2].PropertyId == propertyId); - if (_entries[3].PropertyId >= propertyId) - return (3, _entries[3].PropertyId == propertyId); - if (_entries[4].PropertyId >= propertyId) - return (4, _entries[4].PropertyId == propertyId); - if (_entries[5].PropertyId >= propertyId) - return (5, _entries[5].PropertyId == propertyId); - if (_entries[6].PropertyId >= propertyId) - return (6, _entries[6].PropertyId == propertyId); - if (_entries[7].PropertyId >= propertyId) - return (7, _entries[7].PropertyId == propertyId); - if (_entries[8].PropertyId >= propertyId) - return (8, _entries[8].PropertyId == propertyId); - if (_entries[9].PropertyId >= propertyId) - return (9, _entries[9].PropertyId == propertyId); - if (_entries[10].PropertyId >= propertyId) - return (10, _entries[10].PropertyId == propertyId); + return new EntryIndex(0, found: false); } - else + + // Do a binary search to find the value + while (iHi - iLo > 3) { - int low = 0; - int high = _entries.Length; - int id; + int iPv = (iHi + iLo) / 2; + checkIndex = _entries![iPv].PropertyId; - while (high - low > 3) + if (propertyId == checkIndex) { - int pivot = (high + low) / 2; - id = _entries[pivot].PropertyId; - - if (propertyId == id) - return (pivot, true); - - if (propertyId <= id) - high = pivot; - else - low = pivot + 1; + return new EntryIndex(iPv, found: true); } - do + if (propertyId <= checkIndex) + { + iHi = iPv; + } + else { - id = _entries[low].PropertyId; + iLo = iPv + 1; + } + } - if (id == propertyId) - return (low, true); + // Now we only have three values to search; switch to a linear search + do + { + checkIndex = _entries![iLo].PropertyId; - if (id > propertyId) - break; + if (checkIndex == propertyId) + { + return new EntryIndex(iLo, found: true); + } - ++low; + if (checkIndex > propertyId) + { + // we've gone past the targetIndex - return not found + break; } - while (low < high); - } - return (0, false); + iLo++; + } while (iLo < iHi); + + return new EntryIndex(iLo, found: false); } public bool TryGetValue(AvaloniaProperty property, [MaybeNullWhen(false)] out TValue value) { - (int index, bool found) = TryFindEntry(property.Id); - if (!found) + var entryIndex = LookupEntry(property.Id); + + if (!entryIndex.Found) { value = default; return false; } - value = _entries[index].Value; + value = _entries![entryIndex.Index].Value; + return true; } - public void AddValue(AvaloniaProperty property, TValue value) + private void InsertEntry(Entry entry, int entryIndex) { - Entry[] entries = new Entry[_entries.Length + 1]; - - for (int i = 0; i < _entries.Length; ++i) + if (_entryCount > 0) { - if (_entries[i].PropertyId > property.Id) + if (_entryCount == _entries!.Length) { - if (i > 0) + // We want to have more aggressive resizing when initializing. + var growthFactor = IsInitializing ? 2.0 : 1.2; + + var newSize = (int)(_entryCount * growthFactor); + + if (newSize == _entryCount) { - Array.Copy(_entries, 0, entries, 0, i); + newSize++; } - entries[i] = new Entry { PropertyId = property.Id, Value = value }; - Array.Copy(_entries, i, entries, i + 1, _entries.Length - i); - break; + var destEntries = new Entry[newSize]; + + Array.Copy(_entries, 0, destEntries, 0, entryIndex); + + destEntries[entryIndex] = entry; + + Array.Copy(_entries, entryIndex, destEntries, entryIndex + 1, _entryCount - entryIndex); + + _entries = destEntries; } + else + { + Array.Copy( + _entries, + entryIndex, + _entries, + entryIndex + 1, + _entryCount - entryIndex); + + _entries[entryIndex] = entry; + } + } + else + { + if (_entries is null) + { + _entries = new Entry[InitialSize]; + } + + _entries[0] = entry; } - _entries = entries; + _entryCount++; + } + + public void AddValue(AvaloniaProperty property, TValue value) + { + var propertyId = property.Id; + var index = LookupEntry(propertyId); + + InsertEntry(new Entry(propertyId, value), index.Index); } public void SetValue(AvaloniaProperty property, TValue value) { - _entries[TryFindEntry(property.Id).Item1].Value = value; + var propertyId = property.Id; + var entryIndex = LookupEntry(propertyId); + + _entries![entryIndex.Index] = new Entry(propertyId, value); } public void Remove(AvaloniaProperty property) { - var (index, found) = TryFindEntry(property.Id); + var entry = LookupEntry(property.Id); - if (found) - { - var newLength = _entries.Length - 1; - - // Special case - one element left means that value store is empty so we can just reuse our "empty" array. - if (newLength == 1) - { - _entries = s_emptyEntries; - - return; - } - - var entries = new Entry[newLength]; + if (!entry.Found) return; + + Array.Copy(_entries!, entry.Index + 1, _entries!, entry.Index, _entryCount - entry.Index - 1); - int ix = 0; + _entryCount--; + _entries![_entryCount] = default; + } - for (int i = 0; i < _entries.Length; ++i) - { - if (i != index) - { - entries[ix++] = _entries[i]; - } - } + private readonly struct EntryIndex + { + public readonly int Index; + public readonly bool Found; - _entries = entries; + public EntryIndex(int index, bool found) + { + Index = index; + Found = found; } } - private struct Entry + private readonly struct Entry { - internal int PropertyId; - internal TValue Value; + public readonly int PropertyId; + public readonly TValue Value; + + public Entry(int propertyId, TValue value) + { + PropertyId = propertyId; + Value = value; + } } } } diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index 69c644dff9..664fe6c820 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -24,7 +24,7 @@ namespace Avalonia internal class ValueStore { private readonly AvaloniaObject _owner; - private readonly AvaloniaPropertyValueStore _values; + private AvaloniaPropertyValueStore _values; private BatchUpdate? _batchUpdate; public ValueStore(AvaloniaObject owner) @@ -33,6 +33,12 @@ namespace Avalonia _values = new AvaloniaPropertyValueStore(); } + public bool IsInitializing + { + get => _values.IsInitializing; + set => _values.IsInitializing = value; + } + public void BeginBatchUpdate() { _batchUpdate ??= new BatchUpdate(this); @@ -381,7 +387,7 @@ namespace Avalonia private class BatchUpdate { - private ValueStore _owner; + private readonly ValueStore _owner; private List? _notifications; private int _batchUpdateCount; private int _iterator = -1; @@ -408,7 +414,7 @@ namespace Avalonia if (--_batchUpdateCount > 0) return false; - var values = _owner._values; + ref var values = ref _owner._values; // First call EndBatchUpdate on all bindings. This should cause the active binding to be subscribed // but notifications will still not be raised because the owner ValueStore will still have a reference diff --git a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj index 5d17808e0c..7afc0c569f 100644 --- a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj +++ b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj @@ -5,6 +5,9 @@ Exe false + + + diff --git a/tests/Avalonia.Benchmarks/Base/ValueStoreAddRemoveBenchmarks.cs b/tests/Avalonia.Benchmarks/Base/ValueStoreAddRemoveBenchmarks.cs new file mode 100644 index 0000000000..9397ea79b4 --- /dev/null +++ b/tests/Avalonia.Benchmarks/Base/ValueStoreAddRemoveBenchmarks.cs @@ -0,0 +1,351 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Avalonia.Utilities; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks.Base; + +// TODO: Remove after review together with related benchmark code. +internal sealed class AvaloniaPropertyValueStoreOld +{ + // The last item in the list is always int.MaxValue. + private static readonly Entry[] s_emptyEntries = { new Entry { PropertyId = int.MaxValue, Value = default! } }; + + private Entry[] _entries; + + public AvaloniaPropertyValueStoreOld() + { + _entries = s_emptyEntries; + } + + public int Count => _entries.Length - 1; + public TValue this[int index] => _entries[index].Value; + + private (int, bool) TryFindEntry(int propertyId) + { + if (_entries.Length <= 12) + { + // For small lists, we use an optimized linear search. Since the last item in the list + // is always int.MaxValue, we can skip a conditional branch in each iteration. + // By unrolling the loop, we can skip another unconditional branch in each iteration. + + if (_entries[0].PropertyId >= propertyId) + return (0, _entries[0].PropertyId == propertyId); + if (_entries[1].PropertyId >= propertyId) + return (1, _entries[1].PropertyId == propertyId); + if (_entries[2].PropertyId >= propertyId) + return (2, _entries[2].PropertyId == propertyId); + if (_entries[3].PropertyId >= propertyId) + return (3, _entries[3].PropertyId == propertyId); + if (_entries[4].PropertyId >= propertyId) + return (4, _entries[4].PropertyId == propertyId); + if (_entries[5].PropertyId >= propertyId) + return (5, _entries[5].PropertyId == propertyId); + if (_entries[6].PropertyId >= propertyId) + return (6, _entries[6].PropertyId == propertyId); + if (_entries[7].PropertyId >= propertyId) + return (7, _entries[7].PropertyId == propertyId); + if (_entries[8].PropertyId >= propertyId) + return (8, _entries[8].PropertyId == propertyId); + if (_entries[9].PropertyId >= propertyId) + return (9, _entries[9].PropertyId == propertyId); + if (_entries[10].PropertyId >= propertyId) + return (10, _entries[10].PropertyId == propertyId); + } + else + { + int low = 0; + int high = _entries.Length; + int id; + + while (high - low > 3) + { + int pivot = (high + low) / 2; + id = _entries[pivot].PropertyId; + + if (propertyId == id) + return (pivot, true); + + if (propertyId <= id) + high = pivot; + else + low = pivot + 1; + } + + do + { + id = _entries[low].PropertyId; + + if (id == propertyId) + return (low, true); + + if (id > propertyId) + break; + + ++low; + } + while (low < high); + } + + return (0, false); + } + + public bool TryGetValue(AvaloniaProperty property, [MaybeNullWhen(false)] out TValue value) + { + (int index, bool found) = TryFindEntry(property.Id); + if (!found) + { + value = default; + return false; + } + + value = _entries[index].Value; + return true; + } + + public void AddValue(AvaloniaProperty property, TValue value) + { + Entry[] entries = new Entry[_entries.Length + 1]; + + for (int i = 0; i < _entries.Length; ++i) + { + if (_entries[i].PropertyId > property.Id) + { + if (i > 0) + { + Array.Copy(_entries, 0, entries, 0, i); + } + + entries[i] = new Entry { PropertyId = property.Id, Value = value }; + Array.Copy(_entries, i, entries, i + 1, _entries.Length - i); + break; + } + } + + _entries = entries; + } + + public void SetValue(AvaloniaProperty property, TValue value) + { + _entries[TryFindEntry(property.Id).Item1].Value = value; + } + + public void Remove(AvaloniaProperty property) + { + var (index, found) = TryFindEntry(property.Id); + + if (found) + { + var newLength = _entries.Length - 1; + + // Special case - one element left means that value store is empty so we can just reuse our "empty" array. + if (newLength == 1) + { + _entries = s_emptyEntries; + + return; + } + + var entries = new Entry[newLength]; + + int ix = 0; + + for (int i = 0; i < _entries.Length; ++i) + { + if (i != index) + { + entries[ix++] = _entries[i]; + } + } + + _entries = entries; + } + } + + private struct Entry + { + internal int PropertyId; + internal TValue Value; + } +} + +internal class MockProperty : StyledProperty +{ + public MockProperty([JetBrains.Annotations.NotNull] string name) : base(name, typeof(object), new StyledPropertyMetadata()) + { + } +} + +internal static class MockProperties +{ + public static readonly AvaloniaProperty[] LinearProperties; + public static readonly AvaloniaProperty[] ShuffledProperties; + + static MockProperties() + { + LinearProperties = new AvaloniaProperty[32]; + ShuffledProperties = new AvaloniaProperty[32]; + + for (int i = 0; i < LinearProperties.Length; i++) + { + LinearProperties[i] = ShuffledProperties[i] = new MockProperty($"Property#{i}"); + } + + Shuffle(ShuffledProperties, 42); + } + + private static void Shuffle (T[] array, int seed) + { + var rng = new Random(seed); + + int n = array.Length; + while (n > 1) + { + int k = rng.Next(n--); + T temp = array[n]; + array[n] = array[k]; + array[k] = temp; + } + } +} + +[MemoryDiagnoser] +public class ValueStoreLookupBenchmarks +{ + [Params(2, 6, 10, 20, 30)] + public int PropertyCount; + + [Params(false, true)] + public bool UseShuffledProperties; + + public AvaloniaProperty[] Properties => UseShuffledProperties ? MockProperties.ShuffledProperties : MockProperties.LinearProperties; + + private AvaloniaPropertyValueStore _store; + private AvaloniaPropertyValueStoreOld _oldStore; + + [GlobalSetup] + public void GlobalSetup() + { + _store = new AvaloniaPropertyValueStore(); + _oldStore = new AvaloniaPropertyValueStoreOld(); + + for (int i = 0; i < PropertyCount; i++) + { + _store.AddValue(Properties[i], null); + _oldStore.AddValue(Properties[i], null); + } + } + + [Benchmark] + public void LookupProperties() + { + for (int i = 0; i < PropertyCount; i++) + { + _store.TryGetValue(Properties[i], out _); + } + } + + [Benchmark] public void LookupProperties_Old() + { + for (int i = 0; i < PropertyCount; i++) + { + _oldStore.TryGetValue(Properties[i], out _); + } + } +} + +[MemoryDiagnoser] +public class ValueStoreAddRemoveBenchmarks +{ + [Params(2, 6, 10, 20, 30)] + public int PropertyCount; + + [Params(false, true)] + public bool UseShuffledProperties; + + public AvaloniaProperty[] Properties => UseShuffledProperties ? MockProperties.ShuffledProperties : MockProperties.LinearProperties; + + [Benchmark] + [Arguments(false)] + [Arguments(true)] + public void AddValue(bool isInitializing) + { + var store = new AvaloniaPropertyValueStore { IsInitializing = isInitializing }; + + for (int i = 0; i < PropertyCount; i++) + { + store.AddValue(Properties[i], null); + } + } + + [Benchmark] + [Arguments(false)] + [Arguments(true)] + public void AddAndRemoveValue(bool isInitializing) + { + var store = new AvaloniaPropertyValueStore { IsInitializing = isInitializing }; + + for (int i = 0; i < PropertyCount; i++) + { + store.AddValue(Properties[i], null); + } + + for (int i = PropertyCount - 1; i >= 0; i--) + { + store.Remove(Properties[i]); + } + } + + [Benchmark] + [Arguments(false)] + [Arguments(true)] + public void AddAndRemoveValueInterleaved(bool isInitializing) + { + var store = new AvaloniaPropertyValueStore { IsInitializing = isInitializing }; + + for (int i = 0; i < PropertyCount; i++) + { + store.AddValue(Properties[i], null); + store.Remove(Properties[i]); + } + } + + [Benchmark] + public void AddValue_Old() + { + var store = new AvaloniaPropertyValueStoreOld(); + + for (int i = 0; i < PropertyCount; i++) + { + store.AddValue(Properties[i], null); + } + } + + [Benchmark] + public void AddAndRemoveValue_Old() + { + var store = new AvaloniaPropertyValueStoreOld(); + + for (int i = 0; i < PropertyCount; i++) + { + store.AddValue(Properties[i], null); + } + + for (int i = PropertyCount - 1; i >= 0; i--) + { + store.Remove(Properties[i]); + } + } + + [Benchmark] + public void AddAndRemoveValueInterleaved_Old() + { + var store = new AvaloniaPropertyValueStoreOld(); + + for (int i = 0; i < PropertyCount; i++) + { + store.AddValue(Properties[i], null); + store.Remove(Properties[i]); + } + } +} diff --git a/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs b/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs index 3493dd0f53..e71330f8bd 100644 --- a/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs @@ -39,6 +39,39 @@ namespace Avalonia.Benchmarks.Layout _root.LayoutManager.ExecuteLayoutPass(); } + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public void CreateControl() + { + var control = new Control(); + + _root.Child = control; + + _root.LayoutManager.ExecuteLayoutPass(); + } + + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public void CreateDecorator() + { + var control = new Decorator(); + + _root.Child = control; + + _root.LayoutManager.ExecuteLayoutPass(); + } + + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public void CreateScrollViewer() + { + var control = new ScrollViewer(); + + _root.Child = control; + + _root.LayoutManager.ExecuteLayoutPass(); + } + [Benchmark] [MethodImpl(MethodImplOptions.NoInlining)] public void CreateButton() From 71785b73d8f08be870cfe59b3e96e75f7511d5f7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 19 Jul 2022 10:37:00 +0200 Subject: [PATCH 002/136] Initial refactor of AvaloniaObject value store. Most (but not all) tests passing, all features mostly implemented exception coercion. --- src/Avalonia.Base/Animation/Animatable.cs | 2 +- .../Animation/AnimationInstance`1.cs | 2 +- src/Avalonia.Base/Avalonia.Base.csproj | 1 + src/Avalonia.Base/AvaloniaObject.cs | 440 +++----- src/Avalonia.Base/AvaloniaObjectExtensions.cs | 18 +- src/Avalonia.Base/AvaloniaProperty.cs | 24 +- .../AvaloniaPropertyChangedEventArgs.cs | 24 +- .../AvaloniaPropertyChangedEventArgs`1.cs | 28 +- src/Avalonia.Base/CollectionPolyfills.cs | 33 + src/Avalonia.Base/Data/BindingPriority.cs | 5 + src/Avalonia.Base/Data/BindingValue.cs | 29 + src/Avalonia.Base/DirectPropertyBase.cs | 54 +- src/Avalonia.Base/IStyledPropertyAccessor.cs | 7 + .../PropertyStore/BindingEntry.cs | 191 ++-- .../PropertyStore/BindingEntry`1.cs | 169 ++++ .../PropertyStore/ConstantValueEntry.cs | 82 -- .../PropertyStore/DictionaryPool.cs | 25 + .../PropertyStore/EffectiveValue.cs | 117 +++ .../PropertyStore/EffectiveValue`1.cs | 220 ++++ .../PropertyStore/IBatchUpdate.cs | 8 - .../PropertyStore/IPriorityValueEntry.cs | 18 - src/Avalonia.Base/PropertyStore/IValue.cs | 28 - .../PropertyStore/IValueEntry.cs | 42 + .../PropertyStore/IValueEntry`1.cs | 33 + .../PropertyStore/IValueFrame.cs | 56 ++ .../PropertyStore/ImmediateValueEntry.cs | 44 + .../PropertyStore/ImmediateValueFrame.cs | 60 ++ .../PropertyStore/InheritanceFrame.cs | 34 + .../LocalValueBindingObserver.cs | 59 ++ .../PropertyStore/LocalValueEntry.cs | 41 - .../LocalValueUntypedBindingObserver.cs | 61 ++ .../PropertyStore/LoggingUtils.cs | 61 ++ .../PropertyStore/PriorityValue.cs | 326 ------ .../PropertyStore/UntypedBindingEntry.cs | 163 +++ .../PropertyStore/UntypedValueUtils.cs | 37 + .../PropertyStore/ValueFrameBase.cs | 54 + src/Avalonia.Base/PropertyStore/ValueOwner.cs | 45 - src/Avalonia.Base/PropertyStore/ValueStore.cs | 948 ++++++++++++++++++ src/Avalonia.Base/StyledElement.cs | 130 +-- src/Avalonia.Base/StyledPropertyBase.cs | 84 +- .../Styling/Activators/AndActivator.cs | 17 + .../Styling/Activators/IStyleActivator.cs | 5 + .../Styling/Activators/NotActivator.cs | 1 + .../Styling/Activators/NthChildActivator.cs | 8 +- .../Styling/Activators/OrActivator.cs | 17 + .../Activators/PropertyEqualsActivator.cs | 11 +- .../Styling/Activators/StyleActivatorBase.cs | 4 +- .../Styling/Activators/StyleClassActivator.cs | 8 +- src/Avalonia.Base/Styling/ControlTheme.cs | 1 + .../DirectPropertySetterBindingInstance.cs | 6 + .../Styling/DirectPropertySetterInstance.cs | 12 + src/Avalonia.Base/Styling/ISetter.cs | 6 +- src/Avalonia.Base/Styling/ISetterInstance.cs | 34 +- src/Avalonia.Base/Styling/IStyleInstance.cs | 19 +- src/Avalonia.Base/Styling/IStyleable.cs | 20 - .../Styling/PropertySetterBindingInstance.cs | 197 +--- .../Styling/PropertySetterTemplateInstance.cs | 121 +-- src/Avalonia.Base/Styling/Setter.cs | 80 +- src/Avalonia.Base/Styling/Style.cs | 26 + src/Avalonia.Base/Styling/StyleBase.cs | 22 +- src/Avalonia.Base/Styling/StyleInstance.cs | 143 +-- .../Utilities/AvaloniaPropertyValueStore.cs | 9 +- src/Avalonia.Base/ValueStore.cs | 507 ---------- src/Avalonia.Base/Visual.cs | 33 +- .../Primitives/TemplatedControl.cs | 10 +- .../Animation/AnimatableTests.cs | 22 +- .../AvaloniaObjectTests_BatchUpdate.cs | 695 ------------- .../AvaloniaObjectTests_Binding.cs | 372 ++++--- .../AvaloniaObjectTests_GetValue.cs | 31 +- .../AvaloniaObjectTests_Inheritance.cs | 120 ++- .../AvaloniaObjectTests_OnPropertyChanged.cs | 51 +- .../AvaloniaObjectTests_Validation.cs | 28 +- .../AvaloniaPropertyTests.cs | 16 +- .../PriorityValueTests.cs | 314 ------ .../PropertyStore/ValueStoreTests_Frames.cs | 126 +++ .../Styling/SetterTests.cs | 319 ++++-- .../Styling/StyleTests.cs | 92 +- .../Styling/StyledElementTests.cs | 67 +- .../Avalonia.Benchmarks.csproj | 1 + .../Styling/ControlTheme_Apply.cs | 147 --- .../Styling/Style_Apply.cs | 7 +- .../Styling/Style_ClassSelector.cs | 12 +- .../StyleTests.cs | 21 +- .../Xaml/StyleTests.cs | 2 +- 84 files changed, 3809 insertions(+), 3754 deletions(-) create mode 100644 src/Avalonia.Base/CollectionPolyfills.cs create mode 100644 src/Avalonia.Base/PropertyStore/BindingEntry`1.cs delete mode 100644 src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs create mode 100644 src/Avalonia.Base/PropertyStore/DictionaryPool.cs create mode 100644 src/Avalonia.Base/PropertyStore/EffectiveValue.cs create mode 100644 src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs delete mode 100644 src/Avalonia.Base/PropertyStore/IBatchUpdate.cs delete mode 100644 src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs delete mode 100644 src/Avalonia.Base/PropertyStore/IValue.cs create mode 100644 src/Avalonia.Base/PropertyStore/IValueEntry.cs create mode 100644 src/Avalonia.Base/PropertyStore/IValueEntry`1.cs create mode 100644 src/Avalonia.Base/PropertyStore/IValueFrame.cs create mode 100644 src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs create mode 100644 src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs create mode 100644 src/Avalonia.Base/PropertyStore/InheritanceFrame.cs create mode 100644 src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs delete mode 100644 src/Avalonia.Base/PropertyStore/LocalValueEntry.cs create mode 100644 src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs create mode 100644 src/Avalonia.Base/PropertyStore/LoggingUtils.cs delete mode 100644 src/Avalonia.Base/PropertyStore/PriorityValue.cs create mode 100644 src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs create mode 100644 src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs create mode 100644 src/Avalonia.Base/PropertyStore/ValueFrameBase.cs delete mode 100644 src/Avalonia.Base/PropertyStore/ValueOwner.cs create mode 100644 src/Avalonia.Base/PropertyStore/ValueStore.cs create mode 100644 src/Avalonia.Base/Styling/DirectPropertySetterBindingInstance.cs create mode 100644 src/Avalonia.Base/Styling/DirectPropertySetterInstance.cs delete mode 100644 src/Avalonia.Base/ValueStore.cs delete mode 100644 tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_BatchUpdate.cs delete mode 100644 tests/Avalonia.Base.UnitTests/PriorityValueTests.cs create mode 100644 tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs delete mode 100644 tests/Avalonia.Benchmarks/Styling/ControlTheme_Apply.cs diff --git a/src/Avalonia.Base/Animation/Animatable.cs b/src/Avalonia.Base/Animation/Animatable.cs index b045a32cd1..edaa76233e 100644 --- a/src/Avalonia.Base/Animation/Animatable.cs +++ b/src/Avalonia.Base/Animation/Animatable.cs @@ -235,7 +235,7 @@ namespace Avalonia.Animation private object? GetAnimationBaseValue(AvaloniaProperty property) { - var value = this.GetBaseValue(property, BindingPriority.LocalValue); + var value = this.GetBaseValue(property); if (value == AvaloniaProperty.UnsetValue) { diff --git a/src/Avalonia.Base/Animation/AnimationInstance`1.cs b/src/Avalonia.Base/Animation/AnimationInstance`1.cs index 52cd4b324f..0881fde988 100644 --- a/src/Avalonia.Base/Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Base/Animation/AnimationInstance`1.cs @@ -229,7 +229,7 @@ namespace Avalonia.Animation private void UpdateNeutralValue() { var property = _animator.Property ?? throw new InvalidOperationException("Animator has no property specified."); - var baseValue = _targetControl.GetBaseValue(property, BindingPriority.LocalValue); + var baseValue = _targetControl.GetBaseValue(property); _neutralValue = baseValue != AvaloniaProperty.UnsetValue ? (T)baseValue! : (T)_targetControl.GetValue(property)!; diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 15feed388b..b11a6027f2 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -34,6 +34,7 @@ + diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 6633eabb5d..60e7b2aeef 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -5,7 +5,6 @@ using Avalonia.Data; using Avalonia.Diagnostics; using Avalonia.Logging; using Avalonia.PropertyStore; -using Avalonia.Reactive; using Avalonia.Threading; namespace Avalonia @@ -23,7 +22,7 @@ namespace Avalonia private PropertyChangedEventHandler? _inpcChanged; private EventHandler? _propertyChanged; private List? _inheritanceChildren; - private ValueStore? _values; + private ValueStore _values; private bool _batchUpdate; /// @@ -32,6 +31,7 @@ namespace Avalonia public AvaloniaObject() { VerifyAccess(); + _values = new ValueStore(this); } /// @@ -59,7 +59,7 @@ namespace Avalonia /// /// The inheritance parent. /// - protected AvaloniaObject? InheritanceParent + protected internal AvaloniaObject? InheritanceParent { get { @@ -77,23 +77,8 @@ namespace Avalonia _inheritanceParent?.RemoveInheritanceChild(this); _inheritanceParent = value; - - var properties = AvaloniaPropertyRegistry.Instance.GetRegisteredInherited(GetType()); - var propertiesCount = properties.Count; - - for (var i = 0; i < propertiesCount; i++) - { - var property = properties[i]; - if (valuestore?.IsSet(property) == true) - { - // If local value set there can be no change. - continue; - } - - property.RouteInheritanceParentChanged(this, oldParent); - } - _inheritanceParent?.AddInheritanceChild(this); + _values.SetInheritanceParent(oldParent, value); } } } @@ -118,24 +103,15 @@ namespace Avalonia set { this.Bind(binding.Property!, value); } } - private ValueStore Values - { - get - { - if (_values is null) - { - _values = new ValueStore(this); - - if (_batchUpdate) - _values.BeginBatchUpdate(); - } - - return _values; - } - } - + /// + /// Returns a value indicating whether the current thread is the UI thread. + /// + /// true if the current thread is the UI thread; otherwise false. public bool CheckAccess() => Dispatcher.UIThread.CheckAccess(); + /// + /// Checks that the current thread is the UI thread and throws if not. + /// public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess(); /// @@ -144,9 +120,9 @@ namespace Avalonia /// The property. public void ClearValue(AvaloniaProperty property) { - property = property ?? throw new ArgumentNullException(nameof(property)); - - property.RouteClearValue(this); + _ = property ?? throw new ArgumentNullException(nameof(property)); + VerifyAccess(); + _values.ClearLocalValue(property); } /// @@ -232,12 +208,7 @@ namespace Avalonia /// /// The property. /// The value. - public object? GetValue(AvaloniaProperty property) - { - property = property ?? throw new ArgumentNullException(nameof(property)); - - return property.RouteGetValue(this); - } + public object? GetValue(AvaloniaProperty property) => property.RouteGetValue(this); /// /// Gets a value. @@ -247,10 +218,9 @@ namespace Avalonia /// The value. public T GetValue(StyledPropertyBase property) { - property = property ?? throw new ArgumentNullException(nameof(property)); + _ = property ?? throw new ArgumentNullException(nameof(property)); VerifyAccess(); - - return GetValueOrInheritedOrDefault(property); + return _values.GetValue(property); } /// @@ -269,18 +239,10 @@ namespace Avalonia } /// - public Optional GetBaseValue(StyledPropertyBase property, BindingPriority maxPriority) + public Optional GetBaseValue(StyledPropertyBase property) { - property = property ?? throw new ArgumentNullException(nameof(property)); - VerifyAccess(); - - if (_values is object && - _values.TryGetValue(property, maxPriority, out var value)) - { - return value; - } - - return default; + _ = property ?? throw new ArgumentNullException(nameof(property)); + return _values.GetBaseValue(property); } /// @@ -346,26 +308,19 @@ namespace Avalonia T value, BindingPriority priority = BindingPriority.LocalValue) { - property = property ?? throw new ArgumentNullException(nameof(property)); + _ = property ?? throw new ArgumentNullException(nameof(property)); VerifyAccess(); - LogPropertySet(property, value, priority); + LogPropertySet(property, value, BindingPriority.LocalValue); if (value is UnsetValueType) { if (priority == BindingPriority.LocalValue) - { - Values.ClearLocalValue(property); - } - else - { - throw new NotSupportedException( - "Cannot set property to Unset at non-local value priority."); - } + _values.ClearLocalValue(property); } - else if (!(value is DoNothingType)) + else if (value is not DoNothingType) { - return Values.SetValue(property, value, priority); + return _values.SetValue(property, value, priority); } return null; @@ -389,6 +344,7 @@ namespace Avalonia /// /// Binds a to an observable. /// + /// The type of the property. /// The property. /// The observable. /// The priority of the binding. @@ -398,12 +354,51 @@ namespace Avalonia public IDisposable Bind( AvaloniaProperty property, IObservable source, + BindingPriority priority = BindingPriority.LocalValue) => property.RouteBind(this, source, priority); + + + /// + /// Binds a to an observable. + /// + /// The type of the property. + /// The property. + /// The observable. + /// The priority of the binding. + /// + /// A disposable which can be used to terminate the binding. + /// + public IDisposable Bind( + StyledPropertyBase property, + IObservable source, BindingPriority priority = BindingPriority.LocalValue) { property = property ?? throw new ArgumentNullException(nameof(property)); source = source ?? throw new ArgumentNullException(nameof(source)); + VerifyAccess(); - return property.RouteBind(this, source.ToBindingValue(), priority); + return _values.AddBinding(property, source, priority); + } + + /// + /// Binds a to an observable. + /// + /// The type of the property. + /// The property. + /// The observable. + /// The priority of the binding. + /// + /// A disposable which can be used to terminate the binding. + /// + public IDisposable Bind( + StyledPropertyBase property, + IObservable source, + BindingPriority priority = BindingPriority.LocalValue) + { + property = property ?? throw new ArgumentNullException(nameof(property)); + source = source ?? throw new ArgumentNullException(nameof(source)); + VerifyAccess(); + + return _values.AddBinding(property, source, priority); } /// @@ -425,7 +420,7 @@ namespace Avalonia source = source ?? throw new ArgumentNullException(nameof(source)); VerifyAccess(); - return Values.AddBinding(property, source, priority); + return _values.AddBinding(property, source, priority); } /// @@ -469,29 +464,8 @@ namespace Avalonia /// The property. public void CoerceValue(AvaloniaProperty property) { - _values?.CoerceValue(property); - } - - public void BeginBatchUpdate() - { - if (_batchUpdate) - { - throw new InvalidOperationException("Batch update already in progress."); - } - - _batchUpdate = true; - _values?.BeginBatchUpdate(); - } - - public void EndBatchUpdate() - { - if (!_batchUpdate) - { - throw new InvalidOperationException("No batch update in progress."); - } - - _batchUpdate = false; - _values?.EndBatchUpdate(); + throw new NotImplementedException(); + ////_values?.CoerceValue(property); } /// @@ -507,98 +481,12 @@ namespace Avalonia _inheritanceChildren?.Remove(child); } - internal void InheritedPropertyChanged( - AvaloniaProperty property, - Optional oldValue, - Optional newValue) - { - if (property.Inherits && (_values == null || !_values.IsSet(property))) - { - RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue); - } - } - /// Delegate[]? IAvaloniaObjectDebug.GetPropertyChangedSubscribers() { return _propertyChanged?.GetInvocationList(); } - internal void ValueChanged(AvaloniaPropertyChangedEventArgs change) - { - var property = (StyledPropertyBase)change.Property; - - LogIfError(property, change.NewValue); - - // If the change is to the effective value of the property and no old/new value is set - // then fill in the old/new value from property inheritance/default value. We don't do - // this for non-effective value changes because these are only needed for property - // transitions, where knowing e.g. that an inherited value is active at an arbitrary - // priority isn't of any use and would introduce overhead. - if (change.IsEffectiveValueChange && !change.OldValue.HasValue) - { - change.SetOldValue(GetInheritedOrDefault(property)); - } - - if (change.IsEffectiveValueChange && !change.NewValue.HasValue) - { - change.SetNewValue(GetInheritedOrDefault(property)); - } - - if (!change.IsEffectiveValueChange || - !EqualityComparer.Default.Equals(change.OldValue.Value, change.NewValue.Value)) - { - RaisePropertyChanged(change); - - if (change.IsEffectiveValueChange) - { - Logger.TryGet(LogEventLevel.Verbose, LogArea.Property)?.Log( - this, - "{Property} changed from {$Old} to {$Value} with priority {Priority}", - property, - change.OldValue, - change.NewValue, - change.Priority); - } - } - } - - internal void Completed( - StyledPropertyBase property, - IPriorityValueEntry entry, - Optional oldValue) - { - var change = new AvaloniaPropertyChangedEventArgs( - this, - property, - oldValue, - default, - BindingPriority.Unset); - ValueChanged(change); - } - - /// - /// Called for each inherited property when the changes. - /// - /// The type of the property value. - /// The property. - /// The old inheritance parent. - internal void InheritanceParentChanged( - StyledPropertyBase property, - AvaloniaObject? oldParent) - { - var oldValue = oldParent is not null ? - oldParent.GetValueOrInheritedOrDefault(property) : - property.GetDefaultValue(GetType()); - - var newValue = GetInheritedOrDefault(property); - - if (!EqualityComparer.Default.Equals(oldValue, newValue)) - { - RaisePropertyChanged(property, oldValue, newValue); - } - } - internal AvaloniaPropertyValue GetDiagnosticInternal(AvaloniaProperty property) { if (property.IsDirect) @@ -626,19 +514,23 @@ namespace Avalonia "Unset"); } + internal ValueStore GetValueStore() => _values; + internal IReadOnlyList? GetInheritanceChildren() => _inheritanceChildren; + /// - /// Logs a binding error for a property. + /// Gets a logger to which a binding warning may be written. /// /// The property that the error occurred on. - /// The binding error. - protected internal virtual void LogBindingError(AvaloniaProperty property, Exception e) + /// The binding exception, if any. + /// + /// This is overridden in to prevent logging binding errors when a + /// control is not attached to the visual tree. + /// + internal virtual ParametrizedLogger? GetBindingWarningLogger( + AvaloniaProperty property, + Exception? e) { - Logger.TryGet(LogEventLevel.Warning, LogArea.Binding)?.Log( - this, - "Error in binding to {Target}.{Property}: {Message}", - this, - property, - e.Message); + return Logger.TryGet(LogEventLevel.Warning, LogArea.Binding); } /// @@ -675,6 +567,22 @@ namespace Avalonia { } + // + /// Raises the event for a direct property. + /// + /// The property that has changed. + /// The old property value. + /// The new property value. + /// The priority of the binding that produced the value. + protected void RaisePropertyChanged( + DirectPropertyBase property, + Optional oldValue, + BindingValue newValue, + BindingPriority priority = BindingPriority.LocalValue) + { + RaisePropertyChanged(property, oldValue, newValue, priority, true); + } + /// /// Raises the event. /// @@ -682,18 +590,43 @@ namespace Avalonia /// The old property value. /// The new property value. /// The priority of the binding that produced the value. - protected internal void RaisePropertyChanged( + /// + /// Whether the notification represents a change to the effective value of the property. + /// + internal void RaisePropertyChanged( AvaloniaProperty property, Optional oldValue, BindingValue newValue, - BindingPriority priority = BindingPriority.LocalValue) + BindingPriority priority, + bool isEffectiveValue) { - RaisePropertyChanged(new AvaloniaPropertyChangedEventArgs( - this, - property, - oldValue, - newValue, - priority)); + if (isEffectiveValue) + property.Notifying?.Invoke(this, true); + + try + { + var e = new AvaloniaPropertyChangedEventArgs( + this, + property, + oldValue, + newValue, + priority, + isEffectiveValue); + + OnPropertyChangedCore(e); + + if (isEffectiveValue) + { + property.NotifyChanged(e); + _propertyChanged?.Invoke(this, e); + _inpcChanged?.Invoke(this, new PropertyChangedEventArgs(property.Name)); + } + } + finally + { + if (isEffectiveValue) + property.Notifying?.Invoke(this, false); + } } /// @@ -718,94 +651,10 @@ namespace Avalonia var old = field; field = value; - RaisePropertyChanged(property, old, value); + RaisePropertyChanged(property, old, value, BindingPriority.LocalValue, true); return true; } - private T GetInheritedOrDefault(StyledPropertyBase property) - { - if (property.Inherits && InheritanceParent is AvaloniaObject o) - { - return o.GetValueOrInheritedOrDefault(property); - } - - return property.GetDefaultValue(GetType()); - } - - private T GetValueOrInheritedOrDefault( - StyledPropertyBase property, - BindingPriority maxPriority = BindingPriority.Animation) - { - var o = this; - var inherits = property.Inherits; - var value = default(T); - - while (o != null) - { - var values = o._values; - - if (values != null - && values.TryGetValue(property, maxPriority, out value) == true) - { - return value; - } - - if (!inherits) - { - break; - } - - o = o.InheritanceParent as AvaloniaObject; - } - - return property.GetDefaultValue(GetType()); - } - - protected internal void RaisePropertyChanged(AvaloniaPropertyChangedEventArgs change) - { - VerifyAccess(); - - if (change.IsEffectiveValueChange) - { - change.Property.Notifying?.Invoke(this, true); - } - - try - { - OnPropertyChangedCore(change); - - if (change.IsEffectiveValueChange) - { - change.Property.NotifyChanged(change); - _propertyChanged?.Invoke(this, change); - - if (_inpcChanged != null) - { - var inpce = new PropertyChangedEventArgs(change.Property.Name); - _inpcChanged(this, inpce); - } - - if (change.Property.Inherits && _inheritanceChildren != null) - { - foreach (var child in _inheritanceChildren) - { - child.InheritedPropertyChanged( - change.Property, - change.OldValue, - change.NewValue.ToOptional()); - } - } - } - } - finally - { - if (change.IsEffectiveValueChange) - { - change.Property.Notifying?.Invoke(this, false); - } - } - } - /// /// Sets the value of a direct property. /// @@ -839,7 +688,7 @@ namespace Avalonia throw new ArgumentException($"Property '{property.Name} not registered on '{this.GetType()}"); } - LogIfError(property, value); + LoggingUtils.LogIfNecessary(this, property, value); switch (value.Type) { @@ -877,29 +726,6 @@ namespace Avalonia return description?.Description ?? o.ToString() ?? o.GetType().Name; } - /// - /// Logs a message if the notification represents a binding error. - /// - /// The property being bound. - /// The binding notification. - private void LogIfError(AvaloniaProperty property, BindingValue value) - { - if (value.HasError) - { - if (value.Error is AggregateException aggregate) - { - foreach (var inner in aggregate.InnerExceptions) - { - LogBindingError(property, inner); - } - } - else - { - LogBindingError(property, value.Error!); - } - } - } - /// /// Logs a property set message. /// diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 134e3b2ac7..4ad47ce8c8 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -362,10 +362,8 @@ namespace Avalonia /// /// The object. /// The property. - /// The maximum priority for the value. /// - /// For styled properties, gets the value of the property if set on the object with a - /// priority equal or lower to , otherwise + /// For styled properties, gets the value of the property excluding animated values, otherwise /// . Note that this method does not return /// property values that come from inherited or default values. /// @@ -373,14 +371,13 @@ namespace Avalonia /// public static object? GetBaseValue( this IAvaloniaObject target, - AvaloniaProperty property, - BindingPriority maxPriority) + AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); if (target is AvaloniaObject ao) - return property.RouteGetBaseValue(ao, maxPriority); + return property.RouteGetBaseValue(ao); throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); } @@ -389,10 +386,8 @@ namespace Avalonia /// /// The object. /// The property. - /// The maximum priority for the value. /// - /// For styled properties, gets the value of the property if set on the object with a - /// priority equal or lower to , otherwise + /// For styled properties, gets the value of the property excluding animated values, otherwise /// . Note that this method does not return property values /// that come from inherited or default values. /// @@ -400,8 +395,7 @@ namespace Avalonia /// public static Optional GetBaseValue( this IAvaloniaObject target, - AvaloniaProperty property, - BindingPriority maxPriority) + AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); @@ -410,7 +404,7 @@ namespace Avalonia { return property switch { - StyledPropertyBase styled => ao.GetBaseValue(styled, maxPriority), + StyledPropertyBase styled => ao.GetBaseValue(styled), DirectPropertyBase direct => ao.GetValue(direct), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") }; diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index fd43ced196..304325c791 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Avalonia.Data; using Avalonia.Data.Core; +using Avalonia.PropertyStore; using Avalonia.Styling; using Avalonia.Utilities; @@ -455,6 +456,12 @@ namespace Avalonia return Name; } + /// + /// Creates an effective value for the property. + /// + /// The effective value owner. + internal abstract EffectiveValue CreateEffectiveValue(AvaloniaObject o); + /// /// Routes an untyped ClearValue call to a typed call. /// @@ -471,8 +478,7 @@ namespace Avalonia /// Routes an untyped GetBaseValue call to a typed call. /// /// The object instance. - /// The maximum priority for the value. - internal abstract object? RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority); + internal abstract object? RouteGetBaseValue(AvaloniaObject o); /// /// Routes an untyped SetValue call to a typed call. @@ -496,11 +502,19 @@ namespace Avalonia /// The priority. internal abstract IDisposable RouteBind( AvaloniaObject o, - IObservable> source, + IObservable source, BindingPriority priority); - internal abstract void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject? oldParent); - internal abstract ISetterInstance CreateSetterInstance(IStyleable target, object? value); + /// + /// Routes an untyped Bind call to a typed call. + /// + /// The object instance. + /// The binding source. + /// The priority. + internal abstract IDisposable RouteBind( + AvaloniaObject o, + IObservable> source, + BindingPriority priority); /// /// Overrides the metadata for the property on the specified type. diff --git a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs index 45c67b9f48..a3ca25bc45 100644 --- a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs +++ b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs @@ -17,6 +17,16 @@ namespace Avalonia IsEffectiveValueChange = true; } + internal AvaloniaPropertyChangedEventArgs( + IAvaloniaObject sender, + BindingPriority priority, + bool isEffectiveValueChange) + { + Sender = sender; + Priority = priority; + IsEffectiveValueChange = isEffectiveValueChange; + } + /// /// Gets the that the property changed on. /// @@ -49,20 +59,8 @@ namespace Avalonia /// public BindingPriority Priority { get; private set; } - /// - /// Gets a value indicating whether the change represents a change to the effective value of - /// the property. - /// - /// - /// This will usually be true, except in - /// - /// which receives notifications for all changes to property values, whether a value with a higher - /// priority is present or not. When this property is false, the change that is being signaled - /// has not resulted in a change to the property value on the object. - /// - public bool IsEffectiveValueChange { get; private set; } + internal bool IsEffectiveValueChange { get; private set; } - internal void MarkNonEffectiveValue() => IsEffectiveValueChange = false; protected abstract AvaloniaProperty GetProperty(); protected abstract object? GetOldValue(); protected abstract object? GetNewValue(); diff --git a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs index 734e38596c..2c7a597537 100644 --- a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs +++ b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs @@ -21,7 +21,18 @@ namespace Avalonia Optional oldValue, BindingValue newValue, BindingPriority priority) - : base(sender, priority) + : this(sender, property, oldValue, newValue, priority, true) + { + } + + internal AvaloniaPropertyChangedEventArgs( + IAvaloniaObject sender, + AvaloniaProperty property, + Optional oldValue, + BindingValue newValue, + BindingPriority priority, + bool isEffectiveValueChange) + : base(sender, priority, isEffectiveValueChange) { Property = property; OldValue = oldValue; @@ -39,28 +50,13 @@ namespace Avalonia /// /// Gets the old value of the property. /// - /// - /// When is true, returns the - /// old value of the property on the object. - /// When is false, returns - /// . - /// public new Optional OldValue { get; private set; } /// /// Gets the new value of the property. /// - /// - /// When is true, returns the - /// value of the property on the object. - /// When is false returns the - /// changed value, or if the value was removed. - /// public new BindingValue NewValue { get; private set; } - internal void SetOldValue(Optional value) => OldValue = value; - internal void SetNewValue(BindingValue value) => NewValue = value; - protected override AvaloniaProperty GetProperty() => Property; protected override object? GetOldValue() => OldValue.GetValueOrDefault(AvaloniaProperty.UnsetValue); diff --git a/src/Avalonia.Base/CollectionPolyfills.cs b/src/Avalonia.Base/CollectionPolyfills.cs new file mode 100644 index 0000000000..b41c3a4d2c --- /dev/null +++ b/src/Avalonia.Base/CollectionPolyfills.cs @@ -0,0 +1,33 @@ +#if !NET6_0_OR_GREATER +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Avalonia +{ + internal static class CollectionPolyfills + { + public static bool Remove( + this Dictionary o, + TKey key, + [MaybeNullWhen(false)] out TValue value) + where TKey : notnull + { + if (o.TryGetValue(key, out value)) + return o.Remove(key); + return false; + } + + public static bool TryAdd(this Dictionary o, TKey key, TValue value) + where TKey : notnull + { + if (!o.ContainsKey(key)) + { + o.Add(key, value); + return true; + } + + return false; + } + } +} +#endif diff --git a/src/Avalonia.Base/Data/BindingPriority.cs b/src/Avalonia.Base/Data/BindingPriority.cs index ece64375f2..dd1654f53c 100644 --- a/src/Avalonia.Base/Data/BindingPriority.cs +++ b/src/Avalonia.Base/Data/BindingPriority.cs @@ -35,6 +35,11 @@ namespace Avalonia.Data /// A style binding. /// Style, + + /// + /// The value is inherited from an ancestor element. + /// + Inherited, /// /// The binding is uninitialized. diff --git a/src/Avalonia.Base/Data/BindingValue.cs b/src/Avalonia.Base/Data/BindingValue.cs index 55be611083..247938dec4 100644 --- a/src/Avalonia.Base/Data/BindingValue.cs +++ b/src/Avalonia.Base/Data/BindingValue.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using Avalonia.Utilities; @@ -245,6 +246,34 @@ namespace Avalonia.Data }; } + public static bool operator !=(BindingValue x, Optional y) + { + if (x.HasValue != y.HasValue) + return true; + return !EqualityComparer.Default.Equals(x.Value, y.Value); + } + + public static bool operator ==(BindingValue x, Optional y) + { + if (x.HasValue != y.HasValue) + return false; + return EqualityComparer.Default.Equals(x.Value, y.Value); + } + + public static bool operator !=(Optional x, BindingValue y) + { + if (x.HasValue != y.HasValue) + return true; + return !EqualityComparer.Default.Equals(x.Value, y.Value); + } + + public static bool operator ==(Optional x, BindingValue y) + { + if (x.HasValue != y.HasValue) + return false; + return EqualityComparer.Default.Equals(x.Value, y.Value); + } + /// /// Creates a binding value from an instance of the underlying value type. /// diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index efcb7dfecb..fcb78a9b42 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Data; +using Avalonia.PropertyStore; using Avalonia.Reactive; using Avalonia.Styling; @@ -120,6 +121,11 @@ namespace Avalonia base.OverrideMetadata(type, metadata); } + internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o) + { + throw new InvalidOperationException("Cannot create EffectiveValue for direct property."); + } + /// internal override void RouteClearValue(AvaloniaObject o) { @@ -132,7 +138,7 @@ namespace Avalonia return o.GetValue(this); } - internal override object? RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority) + internal override object? RouteGetBaseValue(AvaloniaObject o) { return o.GetValue(this); } @@ -161,6 +167,22 @@ namespace Avalonia return null; } + /// + /// Routes an untyped Bind call to a typed call. + /// + /// The object instance. + /// The binding source. + /// The priority. + internal override IDisposable RouteBind( + AvaloniaObject o, + IObservable source, + BindingPriority priority) + { + // TODO: this requires a double adapter, we should make AvaloniaObject + // accept an `IObservable` for direct properties directly. + return RouteBind(o, source.ToBindingValue(), priority); + } + /// internal override IDisposable RouteBind( AvaloniaObject o, @@ -170,35 +192,5 @@ namespace Avalonia var adapter = TypedBindingAdapter.Create(o, this, source); return o.Bind(this, adapter); } - - internal override void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject? oldParent) - { - throw new NotSupportedException("Direct properties do not support inheritance."); - } - - internal override ISetterInstance CreateSetterInstance(IStyleable target, object? value) - { - if (value is IBinding binding) - { - return new PropertySetterBindingInstance( - target, - this, - binding); - } - else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType)) - { - return new PropertySetterTemplateInstance( - target, - this, - template); - } - else - { - return new PropertySetterInstance( - target, - this, - (TValue)value!); - } - } } } diff --git a/src/Avalonia.Base/IStyledPropertyAccessor.cs b/src/Avalonia.Base/IStyledPropertyAccessor.cs index c4a0005f55..4cbfd7b759 100644 --- a/src/Avalonia.Base/IStyledPropertyAccessor.cs +++ b/src/Avalonia.Base/IStyledPropertyAccessor.cs @@ -15,5 +15,12 @@ namespace Avalonia /// The default value. /// object? GetDefaultValue(Type type); + + /// + /// Validates the specified property value. + /// + /// The value. + /// True if the value is valid, otherwise false. + bool ValidateValue(object? value); } } diff --git a/src/Avalonia.Base/PropertyStore/BindingEntry.cs b/src/Avalonia.Base/PropertyStore/BindingEntry.cs index 9a25e98a23..d95cd5ed05 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntry.cs @@ -1,154 +1,137 @@ using System; +using System.Diagnostics; +using System.Reactive.Disposables; using Avalonia.Data; -using Avalonia.Threading; namespace Avalonia.PropertyStore { - /// - /// Represents an untyped interface to . - /// - internal interface IBindingEntry : IBatchUpdate, IPriorityValueEntry, IDisposable + internal class BindingEntry : IValueEntry, + IObserver, + IDisposable { - void Start(bool ignoreBatchUpdate); - } - - /// - /// Stores a binding in a or . - /// - /// The property type. - internal class BindingEntry : IBindingEntry, IPriorityValueEntry, IObserver> - { - private readonly AvaloniaObject _owner; - private ValueOwner _sink; + private readonly ValueFrameBase _frame; + private readonly IObservable _source; private IDisposable? _subscription; - private bool _isSubscribed; - private bool _batchUpdate; - private Optional _value; + private bool _hasValue; + private object? _value; public BindingEntry( - AvaloniaObject owner, - StyledPropertyBase property, - IObservable> source, - BindingPriority priority, - ValueOwner sink) + ValueFrameBase frame, + AvaloniaProperty property, + IObservable source) { - _owner = owner; + _frame = frame; + _source = source; Property = property; - Source = source; - Priority = priority; - _sink = sink; } - public StyledPropertyBase Property { get; } - public BindingPriority Priority { get; private set; } - public IObservable> Source { get; } - Optional IValue.GetValue() => _value.ToObject(); - - public void BeginBatchUpdate() => _batchUpdate = true; - - public void EndBatchUpdate() + public bool HasValue { - _batchUpdate = false; - - if (_sink.IsValueStore) - Start(); + get + { + StartIfNecessary(); + return _hasValue; + } } - public Optional GetValue(BindingPriority maxPriority) - { - return Priority >= maxPriority ? _value : Optional.Empty; - } + public AvaloniaProperty Property { get; } public void Dispose() { - _subscription?.Dispose(); - _subscription = null; - OnCompleted(); + Unsubscribe(); + BindingCompleted(); } - public void OnCompleted() + public object? GetValue() { - var oldValue = _value; - _value = default; - Priority = BindingPriority.Unset; - _isSubscribed = false; - _sink.Completed(Property, this, oldValue); + StartIfNecessary(); + if (!_hasValue) + throw new AvaloniaInternalException("The binding entry has no value."); + return _value!; } - public void OnError(Exception error) + public bool TryGetValue(out object? value) { - throw new NotImplementedException("BindingEntry.OnError is not implemented", error); + StartIfNecessary(); + value = _value; + return _hasValue; } - public void OnNext(BindingValue value) + public void Start() { - if (Dispatcher.UIThread.CheckAccess()) - { - UpdateValue(value); - } - else - { - // To avoid allocating closure in the outer scope we need to capture variables - // locally. This allows us to skip most of the allocations when on UI thread. - var instance = this; - var newValue = value; + Debug.Assert(_subscription is null); - Dispatcher.UIThread.Post(() => instance.UpdateValue(newValue)); - } + // Subscription won't be set until Subscribe completes, but in the meantime we + // need to signal that we've started as Subscribe may cause a value to be produced. + _subscription = Disposable.Empty; + _subscription = _source.Subscribe(this); } - public void Start() => Start(false); + public void OnCompleted() => BindingCompleted(); + public void OnError(Exception error) => BindingCompleted(); + + public void OnNext(object? value) => SetValue(value); - public void Start(bool ignoreBatchUpdate) + public virtual void Unsubscribe() { - // We can't use _subscription to check whether we're subscribed because it won't be set - // until Subscribe has finished, which will be too late to prevent reentrancy. In addition - // don't re-subscribe to completed/disposed bindings (indicated by Unset priority). - if (!_isSubscribed && - Priority != BindingPriority.Unset && - (!_batchUpdate || ignoreBatchUpdate)) - { - _isSubscribed = true; - _subscription = Source.Subscribe(this); - } + _subscription?.Dispose(); + _subscription = null; } - public void Reparent(PriorityValue parent) => _sink = new(parent); - - public void RaiseValueChanged( - AvaloniaObject owner, - AvaloniaProperty property, - Optional oldValue, - Optional newValue) + private void ClearValue() { - owner.ValueChanged(new AvaloniaPropertyChangedEventArgs( - owner, - (AvaloniaProperty)property, - oldValue.Cast(), - newValue.Cast(), - Priority)); + if (_hasValue) + { + _hasValue = false; + _value = default; + _frame.Owner?.OnBindingValueCleared(Property, _frame.Priority); + } } - private void UpdateValue(BindingValue value) + private void SetValue(object? value) { - if (value.HasValue && Property.ValidateValue?.Invoke(value.Value) == false) + if (_frame.Owner is null) + return; + + if (value is BindingNotification n) { - value = Property.GetDefaultValue(_owner.GetType()); + value = n.Value; } - if (value.Type == BindingValueType.DoNothing) + if (value == AvaloniaProperty.UnsetValue) { - return; + ClearValue(); } - - var old = _value; - - if (value.Type != BindingValueType.DataValidationError) + else if (value == BindingOperations.DoNothing) + { + // Do nothing! + } + else if (UntypedValueUtils.TryConvertAndValidate(Property, value, out var typedValue)) + { + if (!_hasValue || !Equals(_value, typedValue)) + { + _value = typedValue; + _hasValue = true; + _frame.Owner?.OnBindingValueChanged(Property, _frame.Priority, typedValue); + } + } + else { - _value = value.ToOptional(); + ClearValue(); + LoggingUtils.LogInvalidValue(_frame.Owner.Owner, Property, Property.PropertyType, value); } + } - _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs(_owner, Property, old, value, Priority)); + private void BindingCompleted() + { + _subscription = null; + _frame.OnBindingCompleted(this); + } + + private void StartIfNecessary() + { + if (_subscription is null) + Start(); } } } diff --git a/src/Avalonia.Base/PropertyStore/BindingEntry`1.cs b/src/Avalonia.Base/PropertyStore/BindingEntry`1.cs new file mode 100644 index 0000000000..38b1e42228 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/BindingEntry`1.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reactive.Disposables; +using Avalonia.Data; + +namespace Avalonia.PropertyStore +{ + internal class BindingEntry : IValueEntry, + IObserver, + IObserver>, + IDisposable + { + private readonly ValueFrameBase _frame; + private readonly object _source; + private IDisposable? _subscription; + private bool _hasValue; + private T? _value; + + public BindingEntry( + ValueFrameBase frame, + StyledPropertyBase property, + IObservable> source) + { + _frame = frame; + _source = source; + Property = property; + } + + public BindingEntry( + ValueFrameBase frame, + StyledPropertyBase property, + IObservable source) + { + _frame = frame; + _source = source; + Property = property; + } + + public bool HasValue + { + get + { + StartIfNecessary(); + return _hasValue; + } + } + + public StyledPropertyBase Property { get; } + AvaloniaProperty IValueEntry.Property => Property; + + public void Dispose() + { + Unsubscribe(); + BindingCompleted(); + } + + public T GetValue() + { + StartIfNecessary(); + if (!_hasValue) + throw new AvaloniaInternalException("The binding entry has no value."); + return _value!; + } + + public void Start() + { + Debug.Assert(_subscription is null); + + // Subscription won't be set until Subscribe completes, but in the meantime we + // need to signal that we've started as Subscribe may cause a value to be produced. + _subscription = Disposable.Empty; + + if (_source is IObservable> bv) + _subscription = bv.Subscribe(this); + else if (_source is IObservable b) + _subscription = b.Subscribe(this); + else + throw new AvaloniaInternalException("Unexpected binding source."); + } + + public bool TryGetValue([MaybeNullWhen(false)] out T value) + { + StartIfNecessary(); + value = _value; + return _hasValue; + } + + public void OnCompleted() => BindingCompleted(); + public void OnError(Exception error) => BindingCompleted(); + + public void OnNext(T value) => SetValue(value); + + public void OnNext(BindingValue value) + { + if (_frame.Owner is not null) + LoggingUtils.LogIfNecessary(_frame.Owner.Owner, Property, value); + + if (value.HasValue) + SetValue(value.Value); + else + ClearValue(); + } + + public void Unsubscribe() + { + _subscription?.Dispose(); + _subscription = null; + } + + object? IValueEntry.GetValue() + { + StartIfNecessary(); + if (!_hasValue) + throw new AvaloniaInternalException("The BindingEntry has no value."); + return _value!; + } + + bool IValueEntry.TryGetValue(out object? value) + { + StartIfNecessary(); + value = _value; + return _hasValue; + } + + private void ClearValue() + { + if (_hasValue) + { + _hasValue = false; + _value = default; + _frame.Owner?.OnBindingValueCleared(Property, _frame.Priority); + } + } + + private void SetValue(T value) + { + if (_frame.Owner is null) + return; + + if (Property.ValidateValue?.Invoke(value) != false) + { + if (!_hasValue || !EqualityComparer.Default.Equals(_value, value)) + { + _value = value; + _hasValue = true; + _frame.Owner?.OnBindingValueChanged(Property, _frame.Priority, value); + } + } + else + { + _frame.Owner?.OnBindingValueCleared(Property, _frame.Priority); + } + } + + private void BindingCompleted() + { + _subscription = null; + _frame.OnBindingCompleted(this); + } + + private void StartIfNecessary() + { + if (_subscription is null) + Start(); + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs b/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs deleted file mode 100644 index 4116f4abd9..0000000000 --- a/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using Avalonia.Data; - -namespace Avalonia.PropertyStore -{ - /// - /// Represents an untyped interface to . - /// - internal interface IConstantValueEntry : IPriorityValueEntry, IDisposable - { - } - - /// - /// Stores a value with a priority in a or - /// . - /// - /// The property type. - internal class ConstantValueEntry : IPriorityValueEntry, IConstantValueEntry - { - private ValueOwner _sink; - private Optional _value; - - public ConstantValueEntry( - StyledPropertyBase property, - T value, - BindingPriority priority, - ValueOwner sink) - { - Property = property; - _value = value; - Priority = priority; - _sink = sink; - } - - public ConstantValueEntry( - StyledPropertyBase property, - Optional value, - BindingPriority priority, - ValueOwner sink) - { - Property = property; - _value = value; - Priority = priority; - _sink = sink; - } - - public StyledPropertyBase Property { get; } - public BindingPriority Priority { get; private set; } - Optional IValue.GetValue() => _value.ToObject(); - - public Optional GetValue(BindingPriority maxPriority = BindingPriority.Animation) - { - return Priority >= maxPriority ? _value : Optional.Empty; - } - - public void Dispose() - { - var oldValue = _value; - _value = default; - Priority = BindingPriority.Unset; - _sink.Completed(Property, this, oldValue); - } - - public void Reparent(PriorityValue sink) => _sink = new(sink); - public void Start() { } - - public void RaiseValueChanged( - AvaloniaObject owner, - AvaloniaProperty property, - Optional oldValue, - Optional newValue) - { - owner.ValueChanged(new AvaloniaPropertyChangedEventArgs( - owner, - (AvaloniaProperty)property, - oldValue.Cast(), - newValue.Cast(), - Priority)); - } - } -} diff --git a/src/Avalonia.Base/PropertyStore/DictionaryPool.cs b/src/Avalonia.Base/PropertyStore/DictionaryPool.cs new file mode 100644 index 0000000000..258934b980 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/DictionaryPool.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Avalonia.PropertyStore +{ + internal static class DictionaryPool + where TKey : notnull + { + private const int MaxPoolSize = 4; + private static Stack> _pool = new(); + + public static Dictionary Get() + { + return _pool.Count == 0 ? new() : _pool.Pop(); + } + + public static void Release(Dictionary dictionary) + { + if (_pool.Count < MaxPoolSize) + { + dictionary.Clear(); + _pool.Push(dictionary); + } + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs new file mode 100644 index 0000000000..27d3f319d5 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs @@ -0,0 +1,117 @@ +using System; +using Avalonia.Data; + +namespace Avalonia.PropertyStore +{ + /// + /// Represents the active value for a property in a . + /// + /// + /// This class is an abstract base for the generic . + /// + internal abstract class EffectiveValue + { + /// + /// Gets the current effective value as a boxed value. + /// + public object? Value => GetBoxedValue(); + + /// + /// Gets the current effective base value as a boxed value, or + /// if not set. + /// + public object? BaseValue => GetBoxedBaseValue(); + + /// + /// Gets the priority of the current effective value. + /// + public BindingPriority Priority { get; protected set; } + + /// + /// Gets the priority of the current base value. + /// + public BindingPriority BasePriority { get; protected set; } + + /// + /// Sets the value and base value, raising + /// where necessary. + /// + /// The associated value store. + /// The property being changed. + /// The new value of the property. + /// The priority of the new value. + public abstract void SetAndRaise( + ValueStore owner, + AvaloniaProperty property, + object? value, + BindingPriority priority); + + /// + /// Sets the value and base value, raising + /// where necessary. + /// + /// The associated value store. + /// The property being changed. + /// The new value of the property. + /// The priority of the new value. + /// The new base value of the property. + /// The priority of the new base value. + public abstract void SetAndRaise( + ValueStore owner, + AvaloniaProperty property, + object? value, + BindingPriority priority, + object? baseValue, + BindingPriority basePriority); + + /// + /// Sets the value, raising + /// where necessary. + /// + /// The associated value store. + /// The value entry with the new value of the property. + /// The priority of the new value. + /// + /// This method does not set the base value. + /// + public abstract void SetAndRaise( + ValueStore owner, + IValueEntry entry, + BindingPriority priority); + + /// + /// Set the value priority, but leaves the value unchanged. + /// + public void SetPriority(BindingPriority priority) => Priority = BindingPriority.Unset; + + /// + /// Set the base value priority, but leaves the base value unchanged. + /// + public void SetBasePriority(BindingPriority priority) => BasePriority = BindingPriority.Unset; + + /// + /// Raises in response to an inherited value + /// change. + /// + /// The owner object. + /// The property being changed. + /// The old value of the property. + /// The new value of the property. + public abstract void RaiseInheritedValueChanged( + AvaloniaObject owner, + AvaloniaProperty property, + EffectiveValue? oldValue, + EffectiveValue? newValue); + + /// + /// Disposes the effective value, raising + /// where necessary. + /// + /// The associated value store. + /// The property being cleared. + public abstract void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property); + + protected abstract object? GetBoxedValue(); + protected abstract object? GetBoxedBaseValue(); + } +} diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs new file mode 100644 index 0000000000..2b3865d23d --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -0,0 +1,220 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Avalonia.Data; + +namespace Avalonia.PropertyStore +{ + /// + /// Represents the active value for a property in a . + /// + /// + /// Stores the active value in an 's + /// for a single property, when the value is not inherited or unset/default. + /// + internal sealed class EffectiveValue : EffectiveValue + { + private T? _baseValue; + + public EffectiveValue(T value, BindingPriority priority) + { + Value = value; + Priority = priority; + + if (priority >= BindingPriority.LocalValue && priority < BindingPriority.Inherited) + { + _baseValue = value; + BasePriority = priority; + } + else + { + _baseValue = default; + BasePriority = BindingPriority.Unset; + } + } + + /// + /// Gets the current effective value. + /// + public new T Value { get; private set; } + + public override void SetAndRaise( + ValueStore owner, + AvaloniaProperty property, + object? value, + BindingPriority priority) + { + // `value` should already have been converted to the correct type and + // validated by this point. + SetAndRaise(owner, (StyledPropertyBase)property, (T)value!, priority); + } + + public override void SetAndRaise( + ValueStore owner, + AvaloniaProperty property, + object? value, + BindingPriority priority, + object? baseValue, + BindingPriority basePriority) + { + SetAndRaise(owner, (StyledPropertyBase)property, (T)value!, priority, (T)baseValue!, basePriority); + } + + public override void SetAndRaise( + ValueStore owner, + IValueEntry entry, + BindingPriority priority) + { + var value = entry is IValueEntry typed ? typed.GetValue() : (T)entry.GetValue()!; + SetAndRaise(owner, (StyledPropertyBase)entry.Property, value, priority); + } + + /// + /// Sets the value and base value, raising + /// where necessary. + /// + /// The object on which to raise events. + /// The property being changed. + /// The new value of the property. + /// The priority of the new value. + public void SetAndRaise( + ValueStore owner, + StyledPropertyBase property, + T value, + BindingPriority priority) + { + Debug.Assert(priority < BindingPriority.Inherited); + + var oldValue = Value; + var valueChanged = false; + var baseValueChanged = false; + + if (priority <= Priority) + { + valueChanged = !EqualityComparer.Default.Equals(Value, value); + Value = value; + Priority = priority; + } + + if (priority <= BasePriority && priority >= BindingPriority.LocalValue) + { + baseValueChanged = !EqualityComparer.Default.Equals(_baseValue, value); + _baseValue = value; + BasePriority = priority; + } + + if (valueChanged) + { + owner.Owner.RaisePropertyChanged(property, oldValue, Value, Priority, true); + if (property.Inherits) + owner.OnInheritedEffectiveValueChanged(property, oldValue, this); + } + else if (baseValueChanged) + { + owner.Owner.RaisePropertyChanged(property, default, _baseValue!, BasePriority, false); + } + } + + /// + /// Sets the value and base value, raising + /// where necessary. + /// + /// The object on which to raise events. + /// The property being changed. + /// The new value of the property. + /// The priority of the new value. + /// The new base value of the property. + /// The priority of the new base value. + public void SetAndRaise( + ValueStore owner, + StyledPropertyBase property, + T value, + BindingPriority priority, + T baseValue, + BindingPriority basePriority) + { + Debug.Assert(priority < BindingPriority.Inherited); + Debug.Assert(basePriority > BindingPriority.Animation); + + var oldValue = Value; + var valueChanged = false; + var baseValueChanged = false; + + if (!EqualityComparer.Default.Equals(Value, value)) + { + Value = value; + valueChanged = true; + } + + if (BasePriority == BindingPriority.Unset || + !EqualityComparer.Default.Equals(_baseValue, baseValue)) + { + _baseValue = value; + baseValueChanged = true; + } + + Priority = priority; + BasePriority = basePriority; + + if (valueChanged) + { + owner.Owner.RaisePropertyChanged(property, oldValue, Value, Priority, true); + if (property.Inherits) + owner.OnInheritedEffectiveValueChanged(property, oldValue, this); + } + else if (baseValueChanged) + { + owner.Owner.RaisePropertyChanged(property, default, _baseValue!, BasePriority, false); + } + } + + public bool TryGetBaseValue([MaybeNullWhen(false)] out T value) + { + value = _baseValue!; + return BasePriority != BindingPriority.Unset; + } + + public override void RaiseInheritedValueChanged( + AvaloniaObject owner, + AvaloniaProperty property, + EffectiveValue? oldValue, + EffectiveValue? newValue) + { + Debug.Assert(oldValue is not null || newValue is not null); + + var p = (StyledPropertyBase)property; + var o = oldValue is not null ? ((EffectiveValue)oldValue).Value : p.GetDefaultValue(owner.GetType()); + var n = newValue is not null ? ((EffectiveValue)newValue).Value : p.GetDefaultValue(owner.GetType()); + var priority = newValue is not null ? BindingPriority.Inherited : BindingPriority.Unset; + + if (!EqualityComparer.Default.Equals(o, n)) + { + owner.RaisePropertyChanged(p, o, n, priority, true); + } + } + + public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property) + { + DisposeAndRaiseUnset(owner, (StyledPropertyBase)property); + } + + public void DisposeAndRaiseUnset(ValueStore owner, StyledPropertyBase property) + { + var defaultValue = property.GetDefaultValue(owner.GetType()); + + if (!EqualityComparer.Default.Equals(defaultValue, Value)) + { + owner.Owner.RaisePropertyChanged(property, Value, defaultValue, BindingPriority.Unset, true); + if (property.Inherits) + owner.OnInheritedEffectiveValueDisposed(property, Value); + } + } + + protected override object? GetBoxedValue() => Value; + + protected override object? GetBoxedBaseValue() + { + return BasePriority != BindingPriority.Unset ? _baseValue : AvaloniaProperty.UnsetValue; + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/IBatchUpdate.cs b/src/Avalonia.Base/PropertyStore/IBatchUpdate.cs deleted file mode 100644 index af4faf989c..0000000000 --- a/src/Avalonia.Base/PropertyStore/IBatchUpdate.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Avalonia.PropertyStore -{ - internal interface IBatchUpdate - { - void BeginBatchUpdate(); - void EndBatchUpdate(); - } -} diff --git a/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs b/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs deleted file mode 100644 index 45bbd0cda5..0000000000 --- a/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Avalonia.PropertyStore -{ - /// - /// Represents an untyped interface to . - /// - internal interface IPriorityValueEntry : IValue - { - } - - /// - /// Represents an object that can act as an entry in a . - /// - /// The property type. - internal interface IPriorityValueEntry : IPriorityValueEntry, IValue - { - void Reparent(PriorityValue parent); - } -} diff --git a/src/Avalonia.Base/PropertyStore/IValue.cs b/src/Avalonia.Base/PropertyStore/IValue.cs deleted file mode 100644 index b493df92e6..0000000000 --- a/src/Avalonia.Base/PropertyStore/IValue.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Avalonia.Data; - -namespace Avalonia.PropertyStore -{ - /// - /// Represents an untyped interface to . - /// - internal interface IValue - { - BindingPriority Priority { get; } - Optional GetValue(); - void Start(); - void RaiseValueChanged( - AvaloniaObject owner, - AvaloniaProperty property, - Optional oldValue, - Optional newValue); - } - - /// - /// Represents an object that can act as an entry in a . - /// - /// The property type. - internal interface IValue : IValue - { - Optional GetValue(BindingPriority maxPriority = BindingPriority.Animation); - } -} diff --git a/src/Avalonia.Base/PropertyStore/IValueEntry.cs b/src/Avalonia.Base/PropertyStore/IValueEntry.cs new file mode 100644 index 0000000000..43709fe115 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/IValueEntry.cs @@ -0,0 +1,42 @@ +using System; + +namespace Avalonia.PropertyStore +{ + /// + /// Represents an untyped value entry in an . + /// + internal interface IValueEntry + { + bool HasValue { get; } + + /// + /// Gets the property that this value applies to. + /// + AvaloniaProperty Property { get; } + + /// + /// Gets the value associated with the entry. + /// + /// + /// The entry has no value. + /// + object? GetValue(); + + /// + /// Tries to get the value associated with the entry. + /// + /// + /// When this method returns, contains the value associated with the entry if a value is + /// present; otherwise, returns null. + /// + /// + /// true if the entry has an associated value; otherwise false. + /// + bool TryGetValue(out object? value); + + /// + /// Called when the value entry is removed from the value store. + /// + void Unsubscribe(); + } +} diff --git a/src/Avalonia.Base/PropertyStore/IValueEntry`1.cs b/src/Avalonia.Base/PropertyStore/IValueEntry`1.cs new file mode 100644 index 0000000000..b52ca4815b --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/IValueEntry`1.cs @@ -0,0 +1,33 @@ +namespace Avalonia.PropertyStore +{ + /// + /// Represents a typed value entry in an . + /// + internal interface IValueEntry : IValueEntry + { + /// + /// Gets the property that this value applies to. + /// + new StyledPropertyBase Property { get; } + + /// + /// Gets the value associated with the entry. + /// + /// + /// The entry has no value. + /// + new T GetValue(); + + /// + /// Tries to get the value associated with the entry. + /// + /// + /// When this method returns, contains the value associated with the entry if a value is + /// present; otherwise, returns the default value of . + /// + /// + /// true if the entry has an associated value; otherwise false. + /// + bool TryGetValue(out T? value); + } +} diff --git a/src/Avalonia.Base/PropertyStore/IValueFrame.cs b/src/Avalonia.Base/PropertyStore/IValueFrame.cs new file mode 100644 index 0000000000..99338a4720 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/IValueFrame.cs @@ -0,0 +1,56 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Avalonia.Data; + +namespace Avalonia.PropertyStore +{ + /// + /// Represents a collection of property values in a . + /// + /// + /// A value frame is an abstraction over the following sources of values in an + /// : + /// + /// - A style + /// - Local values + /// - Animation values + /// + internal interface IValueFrame : IDisposable + { + /// + /// Gets the number of value entries in the frame. + /// + int EntryCount { get; } + + /// + /// Gets a value indicating whether the frame is active. + /// + bool IsActive { get; } + + /// + /// Gets the value store that owns the frame. + /// + ValueStore? Owner { get; } + + /// + /// Gets the frame's priority. + /// + BindingPriority Priority { get; } + + /// + /// Retreives the frame's value entry with the specified index. + /// + IValueEntry GetEntry(int index); + + /// + /// Sets the owner of the value frame. + /// + /// The new owner. + void SetOwner(ValueStore? owner); + + /// + /// Tries to retreive the frame's value entry for the specified property. + /// + bool TryGetEntry(AvaloniaProperty property, [NotNullWhen(true)] out IValueEntry? entry); + } +} diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs new file mode 100644 index 0000000000..7c17917393 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs @@ -0,0 +1,44 @@ +using System; + +namespace Avalonia.PropertyStore +{ + internal class ImmediateValueEntry : IValueEntry, IDisposable + { + private readonly ImmediateValueFrame _owner; + private readonly T _value; + + public ImmediateValueEntry( + ImmediateValueFrame owner, + StyledPropertyBase property, + T value) + { + _owner = owner; + _value = value; + Property = property; + } + + public StyledPropertyBase Property { get; } + public bool HasValue => true; + AvaloniaProperty IValueEntry.Property => Property; + + public T GetValue() => _value; + + public bool TryGetValue(out T? value) + { + value = _value; + return true; + } + + public bool TryGetValue(out object? value) + { + value = _value; + return true; + } + + public void Unsubscribe() { } + + public void Dispose() => _owner.OnEntryDisposed(this); + + object? IValueEntry.GetValue() => _value; + } +} diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs new file mode 100644 index 0000000000..94d357ffe4 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs @@ -0,0 +1,60 @@ +using System; +using Avalonia.Data; + +namespace Avalonia.PropertyStore +{ + /// + /// Holds values in a set by one of the SetValue or AddBinding + /// overloads with non-LocalValue priority. + /// + internal class ImmediateValueFrame : ValueFrameBase + { + public ImmediateValueFrame(BindingPriority priority) + { + Priority = priority; + } + + public override bool IsActive => true; + public override BindingPriority Priority { get; } + + public BindingEntry AddBinding( + StyledPropertyBase property, + IObservable> source) + { + var e = new BindingEntry(this, property, source); + Add(e); + return e; + } + + public BindingEntry AddBinding( + StyledPropertyBase property, + IObservable source) + { + var e = new BindingEntry(this, property, source); + Add(e); + return e; + } + + public UntypedBindingEntry AddBinding( + StyledPropertyBase property, + IObservable source) + { + var e = new UntypedBindingEntry(this, property, source); + Add(e); + return e; + } + + public IDisposable AddValue(StyledPropertyBase property, T value) + { + var e = new ImmediateValueEntry(this, property, value); + Add(e); + return e; + } + + public void OnEntryDisposed(IValueEntry value) + { + Remove(value.Property); + Owner?.OnValueEntryRemoved(this, value.Property); + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/InheritanceFrame.cs b/src/Avalonia.Base/PropertyStore/InheritanceFrame.cs new file mode 100644 index 0000000000..3df42ee366 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/InheritanceFrame.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Avalonia.PropertyStore +{ + internal class InheritanceFrame : Dictionary + { + public InheritanceFrame(ValueStore owner, InheritanceFrame? parent = null) + { + Owner = owner; + Parent = parent; + } + + public ValueStore Owner { get; } + public InheritanceFrame? Parent { get; private set; } + + public bool TryGetFromThisOrAncestor(AvaloniaProperty property, [NotNullWhen(true)] out EffectiveValue? value) + { + var frame = this; + + while (frame is object) + { + if (frame.TryGetValue(property, out value)) + return true; + frame = frame.Parent; + } + + value = default; + return false; + } + + public void SetParent(InheritanceFrame? value) => Parent = value; + } +} diff --git a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs new file mode 100644 index 0000000000..4dca6c0100 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs @@ -0,0 +1,59 @@ +using System; +using Avalonia.Data; + +namespace Avalonia.PropertyStore +{ + internal class LocalValueBindingObserver : IObserver, + IObserver>, + IDisposable + { + private readonly ValueStore _owner; + private IDisposable? _subscription; + + public LocalValueBindingObserver(ValueStore owner, StyledPropertyBase property) + { + _owner = owner; + Property = property; + } + + public StyledPropertyBase Property { get;} + + public void Start(IObservable source) + { + _subscription = source.Subscribe(this); + } + + public void Start(IObservable> source) + { + _subscription = source.Subscribe(this); + } + + public void Dispose() + { + _subscription?.Dispose(); + _subscription = null; + _owner.OnLocalValueBindingCompleted(Property, this); + } + + public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this); + public void OnError(Exception error) => OnCompleted(); + + public void OnNext(T value) + { + if (Property.ValidateValue?.Invoke(value) != false) + _owner.SetValue(Property, value, BindingPriority.LocalValue); + else + _owner.ClearLocalValue(Property); + } + + public void OnNext(BindingValue value) + { + LoggingUtils.LogIfNecessary(_owner.Owner, Property, value); + + if (value.HasValue) + _owner.SetValue(Property, value.Value, BindingPriority.LocalValue); + else if (value.Type != BindingValueType.DataValidationError) + _owner.ClearLocalValue(Property); + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs b/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs deleted file mode 100644 index 13ca69681f..0000000000 --- a/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Avalonia.Data; - -namespace Avalonia.PropertyStore -{ - /// - /// Stores a value with local value priority in a or - /// . - /// - /// The property type. - internal class LocalValueEntry : IValue - { - private T _value; - - public LocalValueEntry(T value) => _value = value; - public BindingPriority Priority => BindingPriority.LocalValue; - Optional IValue.GetValue() => new Optional(_value); - - public Optional GetValue(BindingPriority maxPriority) - { - return BindingPriority.LocalValue >= maxPriority ? _value : Optional.Empty; - } - - public void SetValue(T value) => _value = value; - public void Start() { } - - public void RaiseValueChanged( - AvaloniaObject owner, - AvaloniaProperty property, - Optional oldValue, - Optional newValue) - { - owner.ValueChanged(new AvaloniaPropertyChangedEventArgs( - owner, - (AvaloniaProperty)property, - oldValue.Cast(), - newValue.Cast(), - BindingPriority.LocalValue)); - } - } -} diff --git a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs new file mode 100644 index 0000000000..8a745482e7 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs @@ -0,0 +1,61 @@ +using System; +using Avalonia.Data; + +namespace Avalonia.PropertyStore +{ + internal class LocalValueUntypedBindingObserver : IObserver, + IDisposable + { + private readonly ValueStore _owner; + private IDisposable? _subscription; + + public LocalValueUntypedBindingObserver(ValueStore owner, StyledPropertyBase property) + { + _owner = owner; + Property = property; + } + + public StyledPropertyBase Property { get; } + + public void Start(IObservable source) + { + _subscription = source.Subscribe(this); + } + + public void Dispose() + { + _subscription?.Dispose(); + _subscription = null; + _owner.OnLocalValueBindingCompleted(Property, this); + } + + public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this); + public void OnError(Exception error) => OnCompleted(); + + public void OnNext(object? value) + { + if (value is BindingNotification n) + { + value = n.Value; + } + + if (value == AvaloniaProperty.UnsetValue) + { + _owner.ClearLocalValue(Property); + } + else if (value == BindingOperations.DoNothing) + { + // Do nothing! + } + else if (UntypedValueUtils.TryConvertAndValidate(Property, value, out var typedValue)) + { + _owner.SetValue(Property, typedValue, BindingPriority.LocalValue); + } + else + { + _owner.ClearLocalValue(Property); + LoggingUtils.LogInvalidValue(_owner.Owner, Property, typeof(T), value); + } + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/LoggingUtils.cs b/src/Avalonia.Base/PropertyStore/LoggingUtils.cs new file mode 100644 index 0000000000..ecb3a847c1 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/LoggingUtils.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.CompilerServices; +using Avalonia.Data; +using Avalonia.Logging; + +namespace Avalonia.PropertyStore +{ + internal static class LoggingUtils + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LogIfNecessary( + AvaloniaObject owner, + AvaloniaProperty property, + BindingValue value) + { + if (value.HasError) + Log(owner, property, value); + } + + public static void LogInvalidValue( + AvaloniaObject owner, + AvaloniaProperty property, + Type expectedType, + object? value) + { + if (value is not null) + { + owner.GetBindingWarningLogger(property, null)?.Log( + owner, + "Error in binding to {Target}.{Property}: expected {ExpectedType}, got {Value} ({ValueType})", + owner, + property, + expectedType, + value, + value.GetType()); + } + else + { + owner.GetBindingWarningLogger(property, null)?.Log( + owner, + "Error in binding to {Target}.{Property}: expected {ExpectedType}, got null", + owner, + property, + expectedType); + } + } + + private static void Log( + AvaloniaObject owner, + AvaloniaProperty property, + BindingValue value) + { + owner.GetBindingWarningLogger(property, value.Error)?.Log( + owner, + "Error in binding to {Target}.{Property}: {Message}", + owner, + property, + value.Error!.Message); + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/PriorityValue.cs b/src/Avalonia.Base/PropertyStore/PriorityValue.cs deleted file mode 100644 index 182b2638c4..0000000000 --- a/src/Avalonia.Base/PropertyStore/PriorityValue.cs +++ /dev/null @@ -1,326 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia.Data; - -namespace Avalonia.PropertyStore -{ - /// - /// Represents an untyped interface to . - /// - interface IPriorityValue : IValue - { - void UpdateEffectiveValue(); - } - - /// - /// Stores a set of prioritized values and bindings in a . - /// - /// The property type. - /// - /// When more than a single value or binding is applied to a property in an - /// , the entry in the is converted into - /// a . This class holds any number of - /// entries (sorted first by priority and then in the order - /// they were added) plus a local value. - /// - internal class PriorityValue : IPriorityValue, IValue, IBatchUpdate - { - private readonly AvaloniaObject _owner; - private readonly ValueStore _store; - private readonly List> _entries = new List>(); - private readonly Func? _coerceValue; - private Optional _localValue; - private Optional _value; - private bool _isCalculatingValue; - private bool _batchUpdate; - - public PriorityValue( - AvaloniaObject owner, - StyledPropertyBase property, - ValueStore store) - { - _owner = owner; - Property = property; - _store = store; - - if (property.HasCoercion) - { - var metadata = property.GetMetadata(owner.GetType()); - _coerceValue = metadata.CoerceValue; - } - } - - public PriorityValue( - AvaloniaObject owner, - StyledPropertyBase property, - ValueStore store, - IPriorityValueEntry existing) - : this(owner, property, store) - { - existing.Reparent(this); - _entries.Add(existing); - - if (existing is IBindingEntry binding && - existing.Priority == BindingPriority.LocalValue) - { - // Bit of a special case here: if we have a local value binding that is being - // promoted to a priority value we need to make sure the binding is subscribed - // even if we've got a batch operation in progress because otherwise we don't know - // whether the binding or a subsequent SetValue with local priority will win. A - // notification won't be sent during batch update anyway because it will be - // caught and stored for later by the ValueStore. - binding.Start(ignoreBatchUpdate: true); - } - - var v = existing.GetValue(); - - if (v.HasValue) - { - _value = v; - Priority = existing.Priority; - } - } - - public PriorityValue( - AvaloniaObject owner, - StyledPropertyBase property, - ValueStore sink, - LocalValueEntry existing) - : this(owner, property, sink) - { - _value = _localValue = existing.GetValue(BindingPriority.LocalValue); - Priority = BindingPriority.LocalValue; - } - - public StyledPropertyBase Property { get; } - public BindingPriority Priority { get; private set; } = BindingPriority.Unset; - public IReadOnlyList> Entries => _entries; - Optional IValue.GetValue() => _value.ToObject(); - - public void BeginBatchUpdate() - { - _batchUpdate = true; - - foreach (var entry in _entries) - { - (entry as IBatchUpdate)?.BeginBatchUpdate(); - } - } - - public void EndBatchUpdate() - { - _batchUpdate = false; - - foreach (var entry in _entries) - { - (entry as IBatchUpdate)?.EndBatchUpdate(); - } - - UpdateEffectiveValue(null); - } - - public void ClearLocalValue() - { - _localValue = default; - UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs( - _owner, - Property, - default, - default, - BindingPriority.LocalValue)); - } - - public Optional GetValue(BindingPriority maxPriority = BindingPriority.Animation) - { - if (Priority == BindingPriority.Unset) - { - return default; - } - - if (Priority >= maxPriority) - { - return _value; - } - - return CalculateValue(maxPriority).Item1; - } - - public IDisposable? SetValue(T value, BindingPriority priority) - { - IDisposable? result = null; - - if (priority == BindingPriority.LocalValue) - { - _localValue = value; - } - else - { - var insert = FindInsertPoint(priority); - var entry = new ConstantValueEntry(Property, value, priority, new ValueOwner(this)); - _entries.Insert(insert, entry); - result = entry; - } - - UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs( - _owner, - Property, - default, - value, - priority)); - - return result; - } - - public BindingEntry AddBinding(IObservable> source, BindingPriority priority) - { - var binding = new BindingEntry(_owner, Property, source, priority, new(this)); - var insert = FindInsertPoint(binding.Priority); - _entries.Insert(insert, binding); - - if (_batchUpdate) - { - binding.BeginBatchUpdate(); - - if (priority == BindingPriority.LocalValue) - { - binding.Start(ignoreBatchUpdate: true); - } - } - - return binding; - } - - public void UpdateEffectiveValue() => UpdateEffectiveValue(null); - public void Start() => UpdateEffectiveValue(null); - - public void RaiseValueChanged( - AvaloniaObject owner, - AvaloniaProperty property, - Optional oldValue, - Optional newValue) - { - owner.ValueChanged(new AvaloniaPropertyChangedEventArgs( - owner, - (AvaloniaProperty)property, - oldValue.Cast(), - newValue.Cast(), - Priority)); - } - - public void ValueChanged(AvaloniaPropertyChangedEventArgs change) - { - if (change.Priority == BindingPriority.LocalValue) - { - _localValue = default; - } - - if (!_isCalculatingValue && change is AvaloniaPropertyChangedEventArgs c) - { - UpdateEffectiveValue(c); - } - } - - public void Completed(IPriorityValueEntry entry, Optional oldValue) - { - _entries.Remove((IPriorityValueEntry)entry); - UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs( - _owner, - Property, - oldValue, - default, - entry.Priority)); - } - - private int FindInsertPoint(BindingPriority priority) - { - var result = _entries.Count; - - for (var i = 0; i < _entries.Count; ++i) - { - if (_entries[i].Priority < priority) - { - result = i; - break; - } - } - - return result; - } - - public (Optional, BindingPriority) CalculateValue(BindingPriority maxPriority) - { - _isCalculatingValue = true; - - try - { - for (var i = _entries.Count - 1; i >= 0; --i) - { - var entry = _entries[i]; - - if (entry.Priority < maxPriority) - { - continue; - } - - entry.Start(); - - if (entry.Priority >= BindingPriority.LocalValue && - maxPriority <= BindingPriority.LocalValue && - _localValue.HasValue) - { - return (_localValue, BindingPriority.LocalValue); - } - - var entryValue = entry.GetValue(); - - if (entryValue.HasValue) - { - return (entryValue, entry.Priority); - } - } - - if (maxPriority <= BindingPriority.LocalValue && _localValue.HasValue) - { - return (_localValue, BindingPriority.LocalValue); - } - - return (default, BindingPriority.Unset); - } - finally - { - _isCalculatingValue = false; - } - } - - private void UpdateEffectiveValue(AvaloniaPropertyChangedEventArgs? change) - { - var (value, priority) = CalculateValue(BindingPriority.Animation); - - if (value.HasValue && _coerceValue != null) - { - value = _coerceValue(_owner, value.Value); - } - - Priority = priority; - - if (value != _value) - { - var old = _value; - _value = value; - - _store.ValueChanged(new AvaloniaPropertyChangedEventArgs( - _owner, - Property, - old, - value, - Priority)); - } - else if (change is object) - { - change.MarkNonEffectiveValue(); - change.SetOldValue(default); - _store.ValueChanged(change); - } - } - } -} diff --git a/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs new file mode 100644 index 0000000000..c95c6444d0 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reactive.Disposables; +using Avalonia.Data; + +namespace Avalonia.PropertyStore +{ + internal class UntypedBindingEntry : IValueEntry, + IObserver, + IDisposable + { + private readonly ValueFrameBase _frame; + private readonly IObservable _source; + private IDisposable? _subscription; + private bool _hasValue; + private T? _value; + + public UntypedBindingEntry( + ValueFrameBase frame, + StyledPropertyBase property, + IObservable source) + { + _frame = frame; + _source = source; + Property = property; + } + + public bool HasValue + { + get + { + StartIfNecessary(); + return _hasValue; + } + } + + public StyledPropertyBase Property { get; } + AvaloniaProperty IValueEntry.Property => Property; + + public void Dispose() + { + Unsubscribe(); + BindingCompleted(); + } + + public T GetValue() + { + StartIfNecessary(); + if (!_hasValue) + throw new AvaloniaInternalException("The binding entry has no value."); + return _value!; + } + + public void Start() + { + Debug.Assert(_subscription is null); + + // Subscription won't be set until Subscribe completes, but in the meantime we + // need to signal that we've started as Subscribe may cause a value to be produced. + _subscription = Disposable.Empty; + _subscription = _source.Subscribe(this); + } + + public bool TryGetValue([MaybeNullWhen(false)] out T value) + { + StartIfNecessary(); + value = _value; + return _hasValue; + } + + public void OnCompleted() => BindingCompleted(); + public void OnError(Exception error) => BindingCompleted(); + + public void OnNext(object? value) => SetValue(value); + + public void OnNext(BindingValue value) + { + if (value.HasValue) + SetValue(value.Value); + else + ClearValue(); + } + + public void Unsubscribe() + { + _subscription?.Dispose(); + _subscription = null; + } + + object? IValueEntry.GetValue() + { + StartIfNecessary(); + if (!_hasValue) + throw new AvaloniaInternalException("The BindingEntry has no value."); + return _value!; + } + + bool IValueEntry.TryGetValue(out object? value) + { + StartIfNecessary(); + value = _value; + return _hasValue; + } + + private void ClearValue() + { + if (_hasValue) + { + _hasValue = false; + _value = default; + _frame.Owner?.OnBindingValueCleared(Property, _frame.Priority); + } + } + + private void SetValue(object? value) + { + if (_frame.Owner is null) + return; + + if (value is BindingNotification n) + { + value = n.Value; + } + + if (value == AvaloniaProperty.UnsetValue) + { + ClearValue(); + } + else if (value == BindingOperations.DoNothing) + { + // Do nothing! + } + else if (UntypedValueUtils.TryConvertAndValidate(Property, value, out var typedValue)) + { + if (!_hasValue || !EqualityComparer.Default.Equals(_value, typedValue)) + { + _value = typedValue; + _hasValue = true; + _frame.Owner?.OnBindingValueChanged(Property, _frame.Priority, typedValue); + } + } + else + { + ClearValue(); + LoggingUtils.LogInvalidValue(_frame.Owner.Owner, Property, typeof(T), value); + } + } + + private void BindingCompleted() + { + _subscription = null; + _frame.OnBindingCompleted(this); + } + + private void StartIfNecessary() + { + if (_subscription is null) + Start(); + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs b/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs new file mode 100644 index 0000000000..0bdcee3871 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs @@ -0,0 +1,37 @@ +using System.Diagnostics.CodeAnalysis; +using Avalonia.Utilities; + +namespace Avalonia.PropertyStore +{ + internal static class UntypedValueUtils + { + public static bool TryConvertAndValidate( + AvaloniaProperty property, + object? value, + out object? result) + { + if (TypeUtilities.TryConvertImplicit(property.PropertyType, value, out result)) + return ((IStyledPropertyAccessor)property).ValidateValue(result); + + result = default; + return false; + } + + public static bool TryConvertAndValidate( + StyledPropertyBase property, + object? value, + [MaybeNullWhen(false)] out T result) + { + if (TypeUtilities.TryConvertImplicit(typeof(T), value, out var v)) + { + result = (T)v!; + + if (property.ValidateValue?.Invoke(result) != false) + return true; + } + + result = default; + return false; + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/ValueFrameBase.cs b/src/Avalonia.Base/PropertyStore/ValueFrameBase.cs new file mode 100644 index 0000000000..09e37e7503 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/ValueFrameBase.cs @@ -0,0 +1,54 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Avalonia.Data; +using Avalonia.Utilities; + +namespace Avalonia.PropertyStore +{ + internal abstract class ValueFrameBase : IValueFrame + { + private readonly AvaloniaPropertyValueStore _entries = new(); + + public int EntryCount => _entries.Count; + public abstract bool IsActive { get; } + public ValueStore? Owner { get; private set; } + public abstract BindingPriority Priority { get; } + + public bool Contains(AvaloniaProperty property) => _entries.Contains(property); + + public IValueEntry GetEntry(int index) => _entries[index]; + + public void SetOwner(ValueStore? owner) => Owner = owner; + + public bool TryGet(AvaloniaProperty property, [NotNullWhen(true)] out IValueEntry? value) + { + return _entries.TryGetValue(property, out value); + } + + public bool TryGetEntry(AvaloniaProperty property, [NotNullWhen(true)] out IValueEntry? entry) + { + return _entries.TryGetValue(property, out entry); + } + + public void OnBindingCompleted(IValueEntry binding) + { + Remove(binding.Property); + Owner?.OnBindingCompleted(binding.Property, this); + } + + public virtual void Dispose() + { + for (var i = 0; i < _entries.Count; ++i) + _entries[i].Unsubscribe(); + } + + protected void Add(IValueEntry value) + { + Debug.Assert(!value.Property.IsDirect); + _entries.AddValue(value.Property, value); + } + + protected void Remove(AvaloniaProperty property) => _entries.Remove(property); + protected void Set(IValueEntry value) => _entries.SetValue(value.Property, value); + } +} diff --git a/src/Avalonia.Base/PropertyStore/ValueOwner.cs b/src/Avalonia.Base/PropertyStore/ValueOwner.cs deleted file mode 100644 index c68435f7a5..0000000000 --- a/src/Avalonia.Base/PropertyStore/ValueOwner.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Avalonia.Data; - -namespace Avalonia.PropertyStore -{ - /// - /// Represents a union type of and , - /// which are the valid owners of a value store . - /// - /// The value type. - internal readonly struct ValueOwner - { - private readonly ValueStore? _store; - private readonly PriorityValue? _priorityValue; - - public ValueOwner(ValueStore o) - { - _store = o; - _priorityValue = null; - } - - public ValueOwner(PriorityValue v) - { - _store = null; - _priorityValue = v; - } - - public bool IsValueStore => _store is not null; - - public void Completed(StyledPropertyBase property, IPriorityValueEntry entry, Optional oldValue) - { - if (_store is not null) - _store?.Completed(property, entry, oldValue); - else - _priorityValue!.Completed(entry, oldValue); - } - - public void ValueChanged(AvaloniaPropertyChangedEventArgs e) - { - if (_store is not null) - _store?.ValueChanged(e); - else - _priorityValue!.ValueChanged(e); - } - } -} diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs new file mode 100644 index 0000000000..093235955e --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -0,0 +1,948 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Avalonia.Collections.Pooled; +using Avalonia.Data; +using Avalonia.Diagnostics; +using Avalonia.Logging; + +namespace Avalonia.PropertyStore +{ + internal class ValueStore + { + private readonly List _frames = new(); + private Dictionary? _localValueBindings; + private InheritanceFrame? _inheritanceFrame; + private Dictionary? _effectiveValues; + private int _frameGeneration; + private int _styling; + + public ValueStore(AvaloniaObject owner) => Owner = owner; + + public AvaloniaObject Owner { get; } + public IReadOnlyList Frames => _frames; + + public void BeginStyling() => ++_styling; + + public void EndStyling() + { + if (--_styling == 0) + ReevaluateEffectiveValues(); + } + + public void AddFrame(IValueFrame style) + { + InsertFrame(style); + ReevaluateEffectiveValues(); + } + + public IDisposable AddBinding( + StyledPropertyBase property, + IObservable> source, + BindingPriority priority) + { + if (priority == BindingPriority.LocalValue) + { + var observer = new LocalValueBindingObserver(this, property); + DisposeExistingLocalValueBinding(property); + _localValueBindings ??= new(); + _localValueBindings[property.Id] = observer; + observer.Start(source); + return observer; + } + else + { + var effective = GetEffectiveValue(property); + var frame = GetOrCreateImmediateValueFrame(property, priority); + var result = frame.AddBinding(property, source); + + if (effective is null || priority <= effective.Priority) + result.Start(); + + return result; + } + } + + public IDisposable AddBinding( + StyledPropertyBase property, + IObservable source, + BindingPriority priority) + { + if (priority == BindingPriority.LocalValue) + { + var observer = new LocalValueBindingObserver(this, property); + DisposeExistingLocalValueBinding(property); + _localValueBindings ??= new(); + _localValueBindings[property.Id] = observer; + observer.Start(source); + return observer; + } + else + { + var effective = GetEffectiveValue(property); + var frame = GetOrCreateImmediateValueFrame(property, priority); + var result = frame.AddBinding(property, source); + + if (effective is null || priority <= effective.Priority) + result.Start(); + + return result; + } + } + + public IDisposable AddBinding( + StyledPropertyBase property, + IObservable source, + BindingPriority priority) + { + if (priority == BindingPriority.LocalValue) + { + var observer = new LocalValueUntypedBindingObserver(this, property); + DisposeExistingLocalValueBinding(property); + _localValueBindings ??= new(); + _localValueBindings[property.Id] = observer; + observer.Start(source); + return observer; + } + else + { + var effective = GetEffectiveValue(property); + var frame = GetOrCreateImmediateValueFrame(property, priority); + var result = frame.AddBinding(property, source); + + if (effective is null || priority <= effective.Priority) + result.Start(); + + return result; + } + } + + public void ClearLocalValue(AvaloniaProperty property) + { + if (TryGetEffectiveValue(property, out var effective) && + effective.Priority == BindingPriority.LocalValue) + { + ReevaluateEffectiveValue(property, effective, ignoreLocalValue: true); + } + } + + public IDisposable? SetValue(StyledPropertyBase property, T value, BindingPriority priority) + { + if (property.ValidateValue?.Invoke(value) == false) + { + throw new ArgumentException($"{value} is not a valid value for '{property.Name}."); + } + + IDisposable? result = null; + + if (priority != BindingPriority.LocalValue) + { + var frame = GetOrCreateImmediateValueFrame(property, priority); + result = frame.AddValue(property, value); + InsertFrame(frame); + } + + if (TryGetEffectiveValue(property, out var existing)) + { + var effective = (EffectiveValue)existing; + effective.SetAndRaise(this, property, value, priority); + } + else + { + AddEffectiveValueAndRaise(property, value, priority); + } + + return result; + } + + public object? GetValue(AvaloniaProperty property) + { + if (_effectiveValues is not null && _effectiveValues.TryGetValue(property, out var v)) + return v.Value; + if (_inheritanceFrame is not null && _inheritanceFrame.TryGetFromThisOrAncestor(property, out v)) + return v.Value; + + return GetDefaultValue(property); + } + + public T GetValue(StyledPropertyBase property) + { + if (_effectiveValues is not null && _effectiveValues.TryGetValue(property, out var v)) + return ((EffectiveValue)v).Value; + if (_inheritanceFrame is not null && _inheritanceFrame.TryGetFromThisOrAncestor(property, out v)) + return ((EffectiveValue)v).Value; + return property.GetDefaultValue(Owner.GetType()); + } + + public bool IsAnimating(AvaloniaProperty property) + { + if (_effectiveValues is not null && _effectiveValues.TryGetValue(property, out var v)) + return v.Priority <= BindingPriority.Animation; + return false; + } + + public bool IsSet(AvaloniaProperty property) + { + if (_effectiveValues is not null && _effectiveValues.TryGetValue(property, out var v)) + return v.Priority < BindingPriority.Inherited; + return false; + } + + public Optional GetBaseValue(StyledPropertyBase property) + { + if (TryGetEffectiveValue(property, out var v) && + ((EffectiveValue)v).TryGetBaseValue(out var baseValue)) + { + return baseValue; + } + + return default; + } + + public void SetInheritanceParent(AvaloniaObject? oldParent, AvaloniaObject? newParent) + { + var values = DictionaryPool.Get(); + var oldInheritanceFrame = oldParent?.GetValueStore()._inheritanceFrame; + var newInheritanceFrame = newParent?.GetValueStore().OnBecameInheritanceParent(); + + // The old and new parents are the same, nothing to do here. + if (oldInheritanceFrame == newInheritanceFrame) + return; + + // First get the old values from the old inheritance parent. + var f = oldInheritanceFrame; + + while (f is not null) + { + foreach (var i in f) + { + values.TryAdd(i.Key, new(i.Value)); + } + f = f.Parent; + } + + f = newInheritanceFrame; + + // Get the new values from the new inheritance parent. + while (f is not null) + { + foreach (var i in f) + { + if (values.TryGetValue(i.Key, out var existing)) + values[i.Key] = existing.WithNewValue(i.Value); + else + values.Add(i.Key, new(null, i.Value)); + } + f = f.Parent; + } + + ParentInheritanceFrameChanged(newInheritanceFrame); + + // Raise PropertyChanged events where necessary on this object and inheritance children. + foreach (var i in values) + { + var oldValue = i.Value.OldValue; + var newValue = i.Value.NewValue; + + if (oldValue != newValue) + InheritedValueChanged(i.Key, oldValue, newValue); + } + + DictionaryPool.Release(values); + } + + public void FrameActivationChanged(IValueFrame frame) + { + ReevaluateEffectiveValues(); + } + + /// + /// Called by an inheritance child to notify the value store that it has become an + /// inheritance parent. Creates and returns an inheritance frame if necessary. + /// + /// + public InheritanceFrame? OnBecameInheritanceParent() + { + if (_inheritanceFrame is not null) + return _inheritanceFrame; + if (_effectiveValues is null) + return null; + + foreach (var i in _effectiveValues) + { + if (i.Key.Inherits) + return GetOrCreateInheritanceFrame(true); + } + + return null; + } + + /// + /// Called by non-LocalValue binding entries to re-evaluate the effective value when the + /// binding produces a new value. + /// + /// The bound property. + /// The priority of binding which produced a new value. + /// The new value. + public void OnBindingValueChanged( + AvaloniaProperty property, + BindingPriority priority, + object? value) + { + Debug.Assert(priority != BindingPriority.LocalValue); + + if (TryGetEffectiveValue(property, out var existing)) + { + if (priority <= existing.Priority) + ReevaluateEffectiveValue(property, existing); + } + else + { + AddEffectiveValueAndRaise(property, value, priority); + } + } + + /// + /// Called by non-LocalValue binding entries to re-evaluate the effective value when the + /// binding produces a new value. + /// + /// The bound property. + /// The priority of binding which produced a new value. + /// The new value. + public void OnBindingValueChanged( + StyledPropertyBase property, + BindingPriority priority, + T value) + { + Debug.Assert(priority != BindingPriority.LocalValue); + + if (TryGetEffectiveValue(property, out var existing)) + { + if (priority <= existing.Priority) + ReevaluateEffectiveValue(property, existing); + } + else + { + AddEffectiveValueAndRaise(property, value, priority); + } + } + + /// + /// Called by non-LocalValue binding entries to re-evaluate the effective value when the + /// binding produces an unset value. + /// + /// The bound property. + /// The priority of binding which produced a new value. + public void OnBindingValueCleared(AvaloniaProperty property, BindingPriority priority) + { + Debug.Assert(priority != BindingPriority.LocalValue); + + if (TryGetEffectiveValue(property, out var existing)) + { + if (priority <= existing.Priority) + ReevaluateEffectiveValue(property, existing); + } + } + + /// + /// Called by a to re-evaluate the effective value when the + /// binding completes or terminates on error. + /// + /// The previously bound property. + /// The frame which contained the binding. + public void OnBindingCompleted(AvaloniaProperty property, IValueFrame frame) + { + var priority = frame.Priority; + + if (TryGetEffectiveValue(property, out var existing)) + { + if (priority <= existing.Priority) + ReevaluateEffectiveValue(property, existing); + } + } + + /// + /// Called by when an property with inheritance enabled + /// changes its value on this value store. + /// + /// The property whose value changed. + /// The old value of the property. + /// The effective value instance. + public void OnInheritedEffectiveValueChanged( + StyledPropertyBase property, + T oldValue, + EffectiveValue value) + { + Debug.Assert(property.Inherits); + + var children = Owner.GetInheritanceChildren(); + + // If we have children or an existing inheritance frame, then make sure it's owned and + // set the value. If we have no children and no inheritance frame then it will be + // created when it's needed. + if (children is not null || _inheritanceFrame is not null) + GetOrCreateInheritanceFrame(true)[property] = value; + + if (children is not null) + { + var count = children.Count; + + for (var i = 0; i < count; ++i) + { + children[i].GetValueStore().OnParentInheritedValueChanged(property, oldValue, value.Value); + } + } + } + + /// + /// Called by when an property with inheritance enabled + /// is removed from the effective values. + /// + /// The property whose value changed. + /// The old value of the property. + public void OnInheritedEffectiveValueDisposed(StyledPropertyBase property, T oldValue) + { + Debug.Assert(property.Inherits); + Debug.Assert(_inheritanceFrame is null || _inheritanceFrame.Owner == this); + + if (_inheritanceFrame is null || _inheritanceFrame.Owner != this) + return; + + _inheritanceFrame.Remove(property); + + var children = Owner.GetInheritanceChildren(); + + if (children is not null) + { + var defaultValue = property.GetDefaultValue(Owner.GetType()); + var count = children.Count; + + for (var i = 0; i < count; ++i) + { + children[i].GetValueStore().OnParentInheritedValueChanged(property, oldValue, defaultValue); + } + } + } + + /// + /// Called when a completes. + /// + /// The previously bound property. + /// The observer. + public void OnLocalValueBindingCompleted(AvaloniaProperty property, IDisposable observer) + { + if (_localValueBindings is not null && + _localValueBindings.TryGetValue(property.Id, out var existing)) + { + if (existing == observer) + { + _localValueBindings?.Remove(property.Id); + ClearLocalValue(property); + } + } + } + + public void OnParentInheritedValueChanged( + StyledPropertyBase property, + T oldValue, + T newValue) + { + Debug.Assert(property.Inherits); + + // Ensure the inheritance frame is created. + GetOrCreateInheritanceFrame(false); + + // If the inherited value is set locally, propagation stops here. + if (_effectiveValues is not null && _effectiveValues.ContainsKey(property)) + return; + + Owner.RaisePropertyChanged( + property, + oldValue, + newValue, + BindingPriority.Inherited, + true); + + var children = Owner.GetInheritanceChildren(); + + if (children is null) + return; + + var count = children.Count; + + for (var i = 0; i < count; ++i) + { + children[i].GetValueStore().OnParentInheritedValueChanged(property, oldValue, newValue); + } + } + + /// + /// Called by an to re-evaluate the effective value when a value + /// is removed. + /// + /// The frame on which the change occurred. + /// The property whose value was removed. + public void OnValueEntryRemoved(IValueFrame frame, AvaloniaProperty property) + { + Debug.Assert(frame.IsActive); + + if (TryGetEffectiveValue(property, out var existing)) + { + if (frame.Priority <= existing.Priority) + ReevaluateEffectiveValue(property, existing); + } + else + { + Logger.TryGet(LogEventLevel.Error, LogArea.Property)?.Log( + Owner, + "Internal error: ValueStore.OnEntryRemoved called for {Property} " + + "but no effective value was found.", + property); + Debug.Assert(false); + } + } + + public bool RemoveFrame(IValueFrame frame) + { + if (_frames.Remove(frame)) + { + frame.Dispose(); + ++_frameGeneration; + ReevaluateEffectiveValues(); + } + + return false; + } + + public AvaloniaPropertyValue GetDiagnostic(AvaloniaProperty property) + { + var effective = GetEffectiveValue(property); + return new AvaloniaPropertyValue( + property, + effective?.Value, + effective?.Priority ?? BindingPriority.Unset, + null); + } + + private void InsertFrame(IValueFrame frame) + { + var index = _frames.BinarySearch(frame, FrameInsertionComparer.Instance); + if (index < 0) + index = ~index; + _frames.Insert(index, frame); + ++_frameGeneration; + frame.SetOwner(this); + } + + private InheritanceFrame GetOrCreateInheritanceFrame(bool owned) + { + if (_inheritanceFrame is null) + { + var parentFrame = Owner.InheritanceParent?.GetValueStore()._inheritanceFrame; + + _inheritanceFrame = owned || parentFrame is null ? + new(this, parentFrame) : + parentFrame; + + if (_effectiveValues is not null) + { + foreach (var i in _effectiveValues) + { + if (i.Key.Inherits) + _inheritanceFrame[i.Key] = i.Value; + } + } + } + else if (owned && _inheritanceFrame.Owner != this) + { + _inheritanceFrame = new(this, _inheritanceFrame); + } + + return _inheritanceFrame; + } + + private ImmediateValueFrame GetOrCreateImmediateValueFrame( + AvaloniaProperty property, + BindingPriority priority) + { + Debug.Assert(priority != BindingPriority.LocalValue); + + // TODO: Binary search? + for (var i = _frames.Count - 1; i >= 0; --i) + { + var frame = _frames[i]; + if (frame is ImmediateValueFrame immediate && !immediate.Contains(property)) + return immediate; + if (frame.Priority > priority) + break; + } + + var result = new ImmediateValueFrame(priority); + InsertFrame(result); + return result; + } + + private void ReevaluateEffectiveValue( + AvaloniaProperty property, + EffectiveValue current, + bool ignoreLocalValue = false) + { + if (EvaluateEffectiveValue( + property, + !ignoreLocalValue ? current : null, + out var value, + out var priority, + out var baseValue, + out var basePriority)) + { + if (basePriority != BindingPriority.Unset) + current.SetAndRaise(this, property, value, priority, baseValue, basePriority); + else + current.SetAndRaise(this, property, value, priority); + } + else + { + _effectiveValues?.Remove(property); + current.DisposeAndRaiseUnset(this, property); + } + } + + /// + /// Adds a new effective value, raises the initial + /// event and notifies inheritance children if necessary . + /// + /// The property type. + /// The property. + /// The property value. + /// The value priority. + private void AddEffectiveValueAndRaise(AvaloniaProperty property, object? value, BindingPriority priority) + { + Debug.Assert(priority < BindingPriority.Inherited); + var effectiveValue = property.CreateEffectiveValue(Owner); + _effectiveValues ??= new(); + _effectiveValues.Add(property, effectiveValue); + effectiveValue.SetAndRaise(this, property, value, priority); + } + + /// + /// Adds a new effective value, raises the initial + /// event and notifies inheritance children if necessary . + /// + /// The property type. + /// The property. + /// The property value. + /// The value priority. + private void AddEffectiveValueAndRaise(StyledPropertyBase property, T value, BindingPriority priority) + { + Debug.Assert(priority < BindingPriority.Inherited); + var defaultValue = property.GetDefaultValue(Owner.GetType()); + var effectiveValue = new EffectiveValue(defaultValue, BindingPriority.Unset); + _effectiveValues ??= new(); + _effectiveValues.Add(property, effectiveValue); + effectiveValue.SetAndRaise(this, property, value, priority); + } + + /// + /// Evaluates the current value and base value for a property based on the current frames and optionally + /// local values. Does not evaluate inherited values. + /// + /// The property to evaluation + /// The current effective value if the local value is to be considered. + /// When the method exits will contain the current value if it exists. + /// When the method exits will contain the current value priority. + /// >When the method exits will contain the current base value if it exists. + /// When the method exits will contain the current base value priority. + /// + /// True if a value was found, otherwise false. + /// + private bool EvaluateEffectiveValue( + AvaloniaProperty property, + EffectiveValue? current, + out object? value, + out BindingPriority priority, + out object? baseValue, + out BindingPriority basePriority) + { + var i = _frames.Count - 1; + + value = baseValue = AvaloniaProperty.UnsetValue; + priority = basePriority = BindingPriority.Unset; + + // First try to find an animation value. + for (; i >= 0; --i) + { + var frame = _frames[i]; + + if (frame.Priority > BindingPriority.Animation) + break; + + if (frame.IsActive && + frame.TryGetEntry(property, out var entry) && + entry.TryGetValue(out value)) + { + priority = frame.Priority; + --i; + break; + } + } + + // Local values come from the current EffectiveValue. + if (current?.Priority == BindingPriority.LocalValue) + { + // If there's a current effective local value and no animated value then we use the + // effective local value. + if (priority == BindingPriority.Unset) + { + value = current.Value; + priority = BindingPriority.LocalValue; + } + + // The local value is always the base value. + baseValue = current.Value; + basePriority = BindingPriority.LocalValue; + return true; + } + + // Or the current effective base value if there's no longer an animated value. + if (priority == BindingPriority.Unset && current?.BasePriority == BindingPriority.LocalValue) + { + value = baseValue = current.BaseValue; + priority = basePriority = BindingPriority.LocalValue; + return true; + } + + // Now try the rest of the frames. + for (; i >= 0; --i) + { + var frame = _frames[i]; + + if (frame.IsActive && + frame.TryGetEntry(property, out var entry) && + entry.TryGetValue(out var v)) + { + if (priority == BindingPriority.Unset) + { + value = v; + priority = frame.Priority; + } + + baseValue = v; + basePriority = frame.Priority; + return true; + } + } + + return priority != BindingPriority.Unset; + } + + private void InheritedValueChanged( + AvaloniaProperty property, + EffectiveValue? oldValue, + EffectiveValue? newValue) + { + Debug.Assert(oldValue != newValue); + Debug.Assert(oldValue is not null || newValue is not null); + + // If the value is set locally, propagaton ends here. + if (_effectiveValues?.ContainsKey(property) == true) + return; + + // Raise PropertyChanged on this object if necessary. + (oldValue ?? newValue!).RaiseInheritedValueChanged(Owner, property, oldValue, newValue); + + var children = Owner.GetInheritanceChildren(); + + if (children is null) + return; + + var count = children.Count; + + for (var i = 0; i < count; ++i) + { + children[i].GetValueStore().InheritedValueChanged(property, oldValue, newValue); + } + } + + private void ParentInheritanceFrameChanged(InheritanceFrame? parent) + { + if (_inheritanceFrame?.Owner == this) + { + _inheritanceFrame.SetParent(parent); + } + else if (_inheritanceFrame != parent) + { + _inheritanceFrame = parent; + + var children = Owner.GetInheritanceChildren(); + + if (children is null) + return; + + var count = children.Count; + + for (var i = 0; i < count; ++i) + { + children[i].GetValueStore().ParentInheritanceFrameChanged(parent); + } + } + } + + private void ReevaluateEffectiveValues() + { + restart: + // Don't reevaluate if a styling pass is in effect, reevaluation will be done when + // it has finished. + if (_styling > 0) + return; + + var generation = _frameGeneration; + + // Reset all non-LocalValue effective values to Unset priority. + if (_effectiveValues is not null) + { + foreach (var v in _effectiveValues) + { + var e = v.Value; + + if (e.Priority != BindingPriority.LocalValue) + e.SetPriority(BindingPriority.Unset); + if (e.BasePriority != BindingPriority.LocalValue) + e.SetBasePriority(BindingPriority.Unset); + } + } + + // Iterate the frames, setting and creating effective values. + for (var i = _frames.Count - 1; i >= 0; --i) + { + var frame = _frames[i]; + + if (!frame.IsActive) + continue; + + var priority = frame.Priority; + var count = frame.EntryCount; + + for (var j = 0; j < count; ++j) + { + var entry = frame.GetEntry(j); + + if (!entry.HasValue) + continue; + + var property = entry.Property; + + if (_effectiveValues is not null && + _effectiveValues.TryGetValue(property, out var effectiveValue)) + { + if (effectiveValue.Priority == BindingPriority.Unset || + effectiveValue.BasePriority == BindingPriority.Unset) + { + effectiveValue.SetAndRaise(this, entry, priority); + } + } + else + { + var v = property.CreateEffectiveValue(Owner); + _effectiveValues ??= new(); + _effectiveValues.Add(property, v); + v.SetAndRaise(this, entry, priority); + } + + if (generation != _frameGeneration) + goto restart; + } + } + + // Remove all effective values that are still unset. + if (_effectiveValues is not null) + { + PooledList? remove = null; + + foreach (var v in _effectiveValues) + { + var e = v.Value; + + if (e.Priority == BindingPriority.Unset) + { + remove ??= new(); + remove.Add(v.Key); + } + } + + if (remove is not null) + { + foreach (var v in remove) + { + if (_effectiveValues.Remove(v, out var e)) + e.DisposeAndRaiseUnset(this, v); + } + remove.Dispose(); + } + } + } + + [MemberNotNullWhen(true, nameof(_effectiveValues))] + private bool TryGetEffectiveValue( + AvaloniaProperty property, + [NotNullWhen(true)] out EffectiveValue? value) + { + if (_effectiveValues is not null && _effectiveValues.TryGetValue(property, out value)) + return true; + value = null; + return false; + } + + private EffectiveValue? GetEffectiveValue(AvaloniaProperty property) + { + if (_effectiveValues is not null && _effectiveValues.TryGetValue(property, out var value)) + return value; + return null; + } + + private object? GetDefaultValue(AvaloniaProperty property) + { + return ((IStyledPropertyAccessor)property).GetDefaultValue(Owner.GetType()); + } + + private void DisposeExistingLocalValueBinding(AvaloniaProperty property) + { + if (_localValueBindings is not null && + _localValueBindings.TryGetValue(property.Id, out var existing)) + { + existing.Dispose(); + } + } + + private class FrameInsertionComparer : IComparer + { + public static readonly FrameInsertionComparer Instance = new FrameInsertionComparer(); + public int Compare(IValueFrame? x, IValueFrame? y) + { + var result = y!.Priority - x!.Priority; + return result != 0 ? result : -1; + } + } + + private readonly struct OldNewValue + { + public OldNewValue(EffectiveValue? oldValue) + { + OldValue = oldValue; + NewValue = null; + } + + public OldNewValue(EffectiveValue? oldValue, EffectiveValue? newValue) + { + OldValue = oldValue; + NewValue = newValue; + } + + public readonly EffectiveValue? OldValue; + public readonly EffectiveValue? NewValue; + + public OldNewValue WithNewValue(EffectiveValue newValue) => new(OldValue, newValue); + } + } +} diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index ecf5d95ffc..bd012f74d6 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using System.Linq; using Avalonia.Animation; using Avalonia.Collections; using Avalonia.Controls; @@ -69,7 +70,6 @@ namespace Avalonia private IResourceDictionary? _resources; private Styles? _styles; private bool _styled; - private List? _appliedStyles; private ITemplatedControl? _templatedParent; private bool _dataContextUpdating; private bool _hasPromotedTheme; @@ -351,15 +351,21 @@ namespace Avalonia { if (_initCount == 0 && !_styled) { - try - { - BeginBatchUpdate(); - AvaloniaLocator.Current.GetService()?.ApplyStyles(this); - } - finally + var styler = AvaloniaLocator.Current.GetService(); + + if (styler is object) { - _styled = true; - EndBatchUpdate(); + GetValueStore().BeginStyling(); + + try + { + styler.ApplyStyles(this); + } + finally + { + _styled = true; + GetValueStore().EndStyling(); + } } if (_hasPromotedTheme) @@ -389,14 +395,15 @@ namespace Avalonia internal StyleDiagnostics GetStyleDiagnosticsInternal() { - IReadOnlyList? appliedStyles = _appliedStyles; + var styles = new List(); - if (appliedStyles is null) + foreach (var frame in GetValueStore().Frames) { - appliedStyles = Array.Empty(); + if (frame is IStyleInstance style) + styles.Add(style); } - return new StyleDiagnostics(appliedStyles); + return new StyleDiagnostics(styles); } /// @@ -522,20 +529,8 @@ namespace Avalonia return null; } - void IStyleable.StyleApplied(IStyleInstance instance) - { - instance = instance ?? throw new ArgumentNullException(nameof(instance)); - - _appliedStyles ??= new List(); - _appliedStyles.Add(instance); - } - void IStyleable.DetachStyles() => DetachStyles(); - void IStyleable.DetachStyles(IReadOnlyList styles) => DetachStyles(styles); - - void IStyleable.InvalidateStyles() => InvalidateStyles(); - void IStyleHost.StylesAdded(IReadOnlyList styles) { InvalidateStylesOnThisAndDescendents(); @@ -830,56 +825,25 @@ namespace Avalonia } } - private void DetachStyles() + private void DetachStyles(IReadOnlyList? styles = null) { - if (_appliedStyles?.Count > 0) - { - BeginBatchUpdate(); + var valueStore = GetValueStore(); - try - { - foreach (var i in _appliedStyles) - { - i.Dispose(); - } + valueStore.BeginStyling(); - _appliedStyles.Clear(); - } - finally + for (var i = valueStore.Frames.Count - 1; i >= 0; --i) + { + if (valueStore.Frames[i] is StyleInstance si && + (styles is null || styles.Contains(si.Source))) { - EndBatchUpdate(); + valueStore.RemoveFrame(si); } } + valueStore.EndStyling(); _styled = false; } - private void DetachStyles(IReadOnlyList styles) - { - styles = styles ?? throw new ArgumentNullException(nameof(styles)); - - if (_appliedStyles is null) - { - return; - } - - var count = styles.Count; - - for (var i = 0; i < count; ++i) - { - for (var j = _appliedStyles.Count - 1; j >= 0; --j) - { - var applied = _appliedStyles[j]; - - if (applied.Source == styles[i]) - { - applied.Dispose(); - _appliedStyles.RemoveAt(j); - } - } - } - } - private void InvalidateStylesOnThisAndDescendents() { InvalidateStyles(); @@ -895,7 +859,7 @@ namespace Avalonia } } - private void DetachStylesFromThisAndDescendents(IReadOnlyList styles) + private void DetachStylesFromThisAndDescendents(IReadOnlyList styles) { DetachStyles(styles); @@ -927,38 +891,24 @@ namespace Avalonia } } - private static IReadOnlyList RecurseStyles(IReadOnlyList styles) + private static IReadOnlyList RecurseStyles(IReadOnlyList styles) { - var count = styles.Count; - List? result = null; - - for (var i = 0; i < count; ++i) - { - var style = styles[i]; - - if (style.Children.Count > 0) - { - if (result is null) - { - result = new List(styles); - } - - RecurseStyles(style.Children, result); - } - } - - return result ?? styles; + var result = new List(); + RecurseStyles(styles, result); + return result; } - private static void RecurseStyles(IReadOnlyList styles, List result) + private static void RecurseStyles(IReadOnlyList styles, List result) { var count = styles.Count; for (var i = 0; i < count; ++i) { - var style = styles[i]; - result.Add(style); - RecurseStyles(style.Children, result); + var s = styles[i]; + if (s is StyleBase style) + result.Add(style); + else if (s is IReadOnlyList children) + RecurseStyles(children, result); } } } diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs index da607720ff..eed9df71e0 100644 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ b/src/Avalonia.Base/StyledPropertyBase.cs @@ -1,7 +1,10 @@ using System; +using System.Reflection; using Avalonia.Data; +using Avalonia.PropertyStore; using Avalonia.Reactive; using Avalonia.Styling; +using Avalonia.Utilities; namespace Avalonia { @@ -169,6 +172,20 @@ namespace Avalonia /// object? IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type); + bool IStyledPropertyAccessor.ValidateValue(object? value) + { + if (value is null && !typeof(TValue).IsValueType) + return ValidateValue?.Invoke(default!) ?? true; + if (value is TValue typed) + return ValidateValue?.Invoke(typed) ?? true; + return false; + } + + internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o) + { + return new EffectiveValue(GetDefaultValue(o.GetType()), BindingPriority.Unset); + } + /// internal override void RouteClearValue(AvaloniaObject o) { @@ -182,34 +199,44 @@ namespace Avalonia } /// - internal override object? RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority) + internal override object? RouteGetBaseValue(AvaloniaObject o) { - var value = o.GetBaseValue(this, maxPriority); + var value = o.GetBaseValue(this); return value.HasValue ? value.Value : AvaloniaProperty.UnsetValue; } /// internal override IDisposable? RouteSetValue( - AvaloniaObject o, + AvaloniaObject target, object? value, BindingPriority priority) { - var v = TryConvert(value); - - if (v.HasValue) + if (value == BindingOperations.DoNothing) { - return o.SetValue(this, (TValue)v.Value!, priority); + return null; } - else if (v.Type == BindingValueType.UnsetValue) + else if (value == UnsetValue) { - o.ClearValue(this); + target.ClearValue(this); + return null; } - else if (v.HasError) + else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var converted)) { - throw v.Error!; + return target.SetValue(this, (TValue)converted!, priority); } + else + { + var type = value?.GetType().FullName ?? "(null)"; + throw new ArgumentException($"Invalid value for Property '{Name}': '{value}' ({type})"); + } + } - return null; + internal override IDisposable RouteBind( + AvaloniaObject target, + IObservable source, + BindingPriority priority) + { + return target.Bind(this, source, priority); } /// @@ -222,39 +249,6 @@ namespace Avalonia return o.Bind(this, adapter, priority); } - /// - internal override void RouteInheritanceParentChanged( - AvaloniaObject o, - AvaloniaObject? oldParent) - { - o.InheritanceParentChanged(this, oldParent); - } - - internal override ISetterInstance CreateSetterInstance(IStyleable target, object? value) - { - if (value is IBinding binding) - { - return new PropertySetterBindingInstance( - target, - this, - binding); - } - else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType)) - { - return new PropertySetterTemplateInstance( - target, - this, - template); - } - else - { - return new PropertySetterInstance( - target, - this, - (TValue)value!); - } - } - private object? GetDefaultBoxedValue(Type type) { _ = type ?? throw new ArgumentNullException(nameof(type)); diff --git a/src/Avalonia.Base/Styling/Activators/AndActivator.cs b/src/Avalonia.Base/Styling/Activators/AndActivator.cs index 0e1e3b565b..953d0a4953 100644 --- a/src/Avalonia.Base/Styling/Activators/AndActivator.cs +++ b/src/Avalonia.Base/Styling/Activators/AndActivator.cs @@ -16,6 +16,23 @@ namespace Avalonia.Styling.Activators public int Count => _sources?.Count ?? 0; + public override bool IsActive + { + get + { + if (_sources is null) + return false; + + foreach (var source in _sources) + { + if (!source.IsActive) + return false; + } + + return true; + } + } + public void Add(IStyleActivator activator) { _sources ??= new List(); diff --git a/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs b/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs index ac7b8b3ef1..3dee6aaab6 100644 --- a/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs +++ b/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs @@ -18,6 +18,11 @@ namespace Avalonia.Styling.Activators [Unstable] public interface IStyleActivator : IDisposable { + /// + /// Gets a value indicating whether the style is activated. + /// + bool IsActive { get; } + /// /// Subscribes to the activator. /// diff --git a/src/Avalonia.Base/Styling/Activators/NotActivator.cs b/src/Avalonia.Base/Styling/Activators/NotActivator.cs index 1bb6ed3cd2..1735b265d1 100644 --- a/src/Avalonia.Base/Styling/Activators/NotActivator.cs +++ b/src/Avalonia.Base/Styling/Activators/NotActivator.cs @@ -9,6 +9,7 @@ namespace Avalonia.Styling.Activators { private readonly IStyleActivator _source; public NotActivator(IStyleActivator source) => _source = source; + public override bool IsActive => !_source.IsActive; void IStyleActivatorSink.OnNext(bool value, int tag) => PublishNext(!value); protected override void Initialize() => _source.Subscribe(this, 0); protected override void Deinitialize() => _source.Unsubscribe(this); diff --git a/src/Avalonia.Base/Styling/Activators/NthChildActivator.cs b/src/Avalonia.Base/Styling/Activators/NthChildActivator.cs index 6f54cd5904..87f181b884 100644 --- a/src/Avalonia.Base/Styling/Activators/NthChildActivator.cs +++ b/src/Avalonia.Base/Styling/Activators/NthChildActivator.cs @@ -26,9 +26,11 @@ namespace Avalonia.Styling.Activators _reversed = reversed; } + public override bool IsActive => NthChildSelector.Evaluate(_control, _provider, _step, _offset, _reversed).IsMatch; + protected override void Initialize() { - PublishNext(IsMatching()); + PublishNext(IsActive); _provider.ChildIndexChanged += ChildIndexChanged; } @@ -47,10 +49,8 @@ namespace Avalonia.Styling.Activators || e.Child is null || e.Child == _control) { - PublishNext(IsMatching()); + PublishNext(IsActive); } } - - private bool IsMatching() => NthChildSelector.Evaluate(_control, _provider, _step, _offset, _reversed).IsMatch; } } diff --git a/src/Avalonia.Base/Styling/Activators/OrActivator.cs b/src/Avalonia.Base/Styling/Activators/OrActivator.cs index fcb7d71e60..7b206a1d34 100644 --- a/src/Avalonia.Base/Styling/Activators/OrActivator.cs +++ b/src/Avalonia.Base/Styling/Activators/OrActivator.cs @@ -16,6 +16,23 @@ namespace Avalonia.Styling.Activators public int Count => _sources?.Count ?? 0; + public override bool IsActive + { + get + { + if (_sources is null) + return false; + + foreach (var source in _sources) + { + if (source.IsActive) + return true; + } + + return false; + } + } + public void Add(IStyleActivator activator) { _sources ??= new List(); diff --git a/src/Avalonia.Base/Styling/Activators/PropertyEqualsActivator.cs b/src/Avalonia.Base/Styling/Activators/PropertyEqualsActivator.cs index 69de665485..d98959d40b 100644 --- a/src/Avalonia.Base/Styling/Activators/PropertyEqualsActivator.cs +++ b/src/Avalonia.Base/Styling/Activators/PropertyEqualsActivator.cs @@ -24,6 +24,15 @@ namespace Avalonia.Styling.Activators _value = value; } + public override bool IsActive + { + get + { + var value = _control.GetValue(_property); + return PropertyEqualsSelector.Compare(_property.PropertyType, value, _value); + } + } + protected override void Initialize() { _subscription = _control.GetObservable(_property).Subscribe(this); @@ -33,6 +42,6 @@ namespace Avalonia.Styling.Activators void IObserver.OnCompleted() { } void IObserver.OnError(Exception error) { } - void IObserver.OnNext(object? value) => PublishNext(PropertyEqualsSelector.Compare(_property.PropertyType, value, _value)); + void IObserver.OnNext(object? value) => PublishNext(IsActive); } } diff --git a/src/Avalonia.Base/Styling/Activators/StyleActivatorBase.cs b/src/Avalonia.Base/Styling/Activators/StyleActivatorBase.cs index 578098b2b0..c9645dcbc2 100644 --- a/src/Avalonia.Base/Styling/Activators/StyleActivatorBase.cs +++ b/src/Avalonia.Base/Styling/Activators/StyleActivatorBase.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace Avalonia.Styling.Activators { /// @@ -11,6 +9,8 @@ namespace Avalonia.Styling.Activators private int _tag; private bool? _value; + public abstract bool IsActive { get; } + public void Subscribe(IStyleActivatorSink sink, int tag = 0) { if (_sink is null) diff --git a/src/Avalonia.Base/Styling/Activators/StyleClassActivator.cs b/src/Avalonia.Base/Styling/Activators/StyleClassActivator.cs index 3f70ff50b3..de27e797ea 100644 --- a/src/Avalonia.Base/Styling/Activators/StyleClassActivator.cs +++ b/src/Avalonia.Base/Styling/Activators/StyleClassActivator.cs @@ -22,6 +22,8 @@ namespace Avalonia.Styling.Activators _match = match; } + public override bool IsActive => AreClassesMatching(_classes, _match); + public static bool AreClassesMatching(IReadOnlyList classes, IList toMatch) { int remainingMatches = toMatch.Count; @@ -54,12 +56,12 @@ namespace Avalonia.Styling.Activators void IClassesChangedListener.Changed() { - PublishNext(IsMatching()); + PublishNext(IsActive); } protected override void Initialize() { - PublishNext(IsMatching()); + PublishNext(IsActive); _classes.AddListener(this); } @@ -67,7 +69,5 @@ namespace Avalonia.Styling.Activators { _classes.RemoveListener(this); } - - private bool IsMatching() => AreClassesMatching(_classes, _match); } } diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs index 644e8b32d4..8fd94297b6 100644 --- a/src/Avalonia.Base/Styling/ControlTheme.cs +++ b/src/Avalonia.Base/Styling/ControlTheme.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.PropertyStore; namespace Avalonia.Styling { diff --git a/src/Avalonia.Base/Styling/DirectPropertySetterBindingInstance.cs b/src/Avalonia.Base/Styling/DirectPropertySetterBindingInstance.cs new file mode 100644 index 0000000000..a95835b7fc --- /dev/null +++ b/src/Avalonia.Base/Styling/DirectPropertySetterBindingInstance.cs @@ -0,0 +1,6 @@ +namespace Avalonia.Styling +{ + internal class DirectPropertySetterBindingInstance : ISetterInstance + { + } +} diff --git a/src/Avalonia.Base/Styling/DirectPropertySetterInstance.cs b/src/Avalonia.Base/Styling/DirectPropertySetterInstance.cs new file mode 100644 index 0000000000..1707be454f --- /dev/null +++ b/src/Avalonia.Base/Styling/DirectPropertySetterInstance.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Styling +{ + internal class DirectPropertySetterInstance : ISetterInstance + { + } +} diff --git a/src/Avalonia.Base/Styling/ISetter.cs b/src/Avalonia.Base/Styling/ISetter.cs index 71ae5d84c0..87ba6c5680 100644 --- a/src/Avalonia.Base/Styling/ISetter.cs +++ b/src/Avalonia.Base/Styling/ISetter.cs @@ -12,13 +12,13 @@ namespace Avalonia.Styling /// /// Instances a setter on a control. /// + /// The style which contains the setter. /// The control. /// An . /// /// This method should return an which can be used to apply - /// the setter to the specified control. Note that it should not apply the setter value - /// until is called. + /// the setter to the specified control. /// - ISetterInstance Instance(IStyleable target); + ISetterInstance Instance(IStyleInstance styleInstance, IStyleable target); } } diff --git a/src/Avalonia.Base/Styling/ISetterInstance.cs b/src/Avalonia.Base/Styling/ISetterInstance.cs index e0d3137619..4a65d6deeb 100644 --- a/src/Avalonia.Base/Styling/ISetterInstance.cs +++ b/src/Avalonia.Base/Styling/ISetterInstance.cs @@ -1,40 +1,12 @@ -using System; -using Avalonia.Metadata; +using Avalonia.Metadata; namespace Avalonia.Styling { /// - /// Represents a setter that has been instanced on a control. + /// Represents an that has been instanced on a control. /// [Unstable] - public interface ISetterInstance : IDisposable + public interface ISetterInstance { - /// - /// Starts the setter instance. - /// - /// Whether the parent style has an activator. - /// - /// If is false then the setter should be immediately - /// applied and and should not be called. - /// If true, then bindings etc should be initiated but not produce a value until - /// called. - /// - public void Start(bool hasActivator); - - /// - /// Activates the setter. - /// - /// - /// Should only be called if hasActivator was true when was called. - /// - public void Activate(); - - /// - /// Deactivates the setter. - /// - /// - /// Should only be called if hasActivator was true when was called. - /// - public void Deactivate(); } } diff --git a/src/Avalonia.Base/Styling/IStyleInstance.cs b/src/Avalonia.Base/Styling/IStyleInstance.cs index 262f336e05..749a2c84d5 100644 --- a/src/Avalonia.Base/Styling/IStyleInstance.cs +++ b/src/Avalonia.Base/Styling/IStyleInstance.cs @@ -1,13 +1,12 @@ -using System; -using Avalonia.Metadata; +using Avalonia.Metadata; namespace Avalonia.Styling { /// - /// Represents a style that has been instanced on a control. + /// Represents a that has been instanced on a control. /// [Unstable] - public interface IStyleInstance : IDisposable + public interface IStyleInstance { /// /// Gets the source style. @@ -15,18 +14,16 @@ namespace Avalonia.Styling IStyle Source { get; } /// - /// Gets a value indicating whether this style has an activator. + /// Gets a value indicating whether this style instance has an activator. /// + /// + /// A style instance without an activator will always be active. + /// bool HasActivator { get; } - + /// /// Gets a value indicating whether this style is active. /// bool IsActive { get; } - - /// - /// Instructs the style to start acting upon the control. - /// - void Start(); } } diff --git a/src/Avalonia.Base/Styling/IStyleable.cs b/src/Avalonia.Base/Styling/IStyleable.cs index 254da4d85c..e94fc5c4e6 100644 --- a/src/Avalonia.Base/Styling/IStyleable.cs +++ b/src/Avalonia.Base/Styling/IStyleable.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Avalonia.Collections; using Avalonia.Metadata; @@ -31,25 +30,6 @@ namespace Avalonia.Styling /// ControlTheme? GetEffectiveTheme(); - /// - /// Notifies the element that a style has been applied. - /// - /// The style instance. - void StyleApplied(IStyleInstance instance); - - /// - /// Detaches all styles applied to the element. - /// void DetachStyles(); - - /// - /// Detaches a collection of styles, if applied to the element. - /// - void DetachStyles(IReadOnlyList styles); - - /// - /// Detaches all styles from the element and queues a restyle. - /// - void InvalidateStyles(); } } diff --git a/src/Avalonia.Base/Styling/PropertySetterBindingInstance.cs b/src/Avalonia.Base/Styling/PropertySetterBindingInstance.cs index edd3fb7d48..18d23ab70b 100644 --- a/src/Avalonia.Base/Styling/PropertySetterBindingInstance.cs +++ b/src/Avalonia.Base/Styling/PropertySetterBindingInstance.cs @@ -1,200 +1,41 @@ using System; -using System.Reactive.Subjects; +using System.Reactive.Linq; using Avalonia.Data; -using Avalonia.Reactive; - -#nullable enable +using Avalonia.PropertyStore; namespace Avalonia.Styling { - /// - /// A which has been instanced on a control and has an - /// as its value. - /// - /// The target property type. - internal class PropertySetterBindingInstance : SingleSubscriberObservableBase>, - ISubject>, - ISetterInstance + internal class PropertySetterBindingInstance : BindingEntry, ISetterInstance { - private readonly IStyleable _target; - private readonly StyledPropertyBase? _styledProperty; - private readonly DirectPropertyBase? _directProperty; - private readonly InstancedBinding? _binding; - private readonly Inner _inner; - private BindingValue _value; - private IDisposable? _subscription; - private IDisposable? _subscriptionTwoWay; - private IDisposable? _innerSubscription; - private bool _isActive; - - public PropertySetterBindingInstance( - IStyleable target, - StyledPropertyBase property, - IBinding binding) - { - _target = target; - _styledProperty = property; - _binding = binding.Initiate(_target, property); - - if (_binding?.Mode == BindingMode.OneTime) - { - // For the moment, we don't support OneTime bindings in setters, because I'm not - // sure what the semantics should be in the case of activation/deactivation. - throw new NotSupportedException("OneTime bindings are not supported in setters."); - } - - _inner = new Inner(this); - } + private readonly IDisposable? _twoWaySubscription; public PropertySetterBindingInstance( - IStyleable target, - DirectPropertyBase property, - IBinding binding) - { - _target = target; - _directProperty = property; - _binding = binding.Initiate(_target, property); - _inner = new Inner(this); - } - - public void Start(bool hasActivator) - { - if (_binding is null) - return; - - _isActive = !hasActivator; - - if (_styledProperty is object) - { - if (_binding.Mode != BindingMode.OneWayToSource) - { - var priority = hasActivator ? BindingPriority.StyleTrigger : BindingPriority.Style; - _subscription = _target.Bind(_styledProperty, this, priority); - } - - if (_binding.Mode == BindingMode.TwoWay) - { - _subscriptionTwoWay = _target.GetBindingObservable(_styledProperty).Subscribe(this); - } - } - else - { - if (_binding.Mode != BindingMode.OneWayToSource) - { - _subscription = _target.Bind(_directProperty!, this); - } - - if (_binding.Mode == BindingMode.TwoWay) - { - _subscriptionTwoWay = _target.GetBindingObservable(_directProperty!).Subscribe(this); - } - } - } - - public void Activate() - { - if (_binding is null) - return; - - if (!_isActive) - { - _innerSubscription ??= _binding.Observable!.Subscribe(_inner); - _isActive = true; - PublishNext(); - } - } - - public void Deactivate() + AvaloniaObject target, + StyleInstance instance, + AvaloniaProperty property, + BindingMode mode, + IObservable source) + : base(instance, property, source) { - if (_isActive) + if (mode == BindingMode.TwoWay) { - _isActive = false; - _innerSubscription?.Dispose(); - _innerSubscription = null; - PublishNext(); - } - } - - public override void Dispose() - { - if (_subscription is object) - { - var sub = _subscription; - _subscription = null; - sub.Dispose(); - } - - if (_subscriptionTwoWay is object) - { - var sub = _subscriptionTwoWay; - _subscriptionTwoWay = null; - sub.Dispose(); - } - - base.Dispose(); - } - - void IObserver>.OnCompleted() - { - // This is the observable coming from the target control. It should not complete. - } - - void IObserver>.OnError(Exception error) - { - // This is the observable coming from the target control. It should not error. - } - - void IObserver>.OnNext(BindingValue value) - { - if (value.HasValue && _isActive && _binding?.Subject is not null) - { - _binding.Subject.OnNext(value.Value); - } - } - - protected override void Subscribed() - { - if (_isActive && _binding?.Observable is not null) - { - if (_innerSubscription is null) + // TODO: HUGE HACK FIXME + if (source is IObserver observer) { - _innerSubscription ??= _binding.Observable!.Subscribe(_inner); + _twoWaySubscription = target.GetObservable(property).Skip(1).Subscribe(observer); } else { - PublishNext(); + throw new NotSupportedException( + "Attempting to bind two-way with a binding source which doesn't support it."); } } } - protected override void Unsubscribed() - { - _innerSubscription?.Dispose(); - _innerSubscription = null; - } - - private void PublishNext() - { - PublishNext(_isActive ? _value : default); - } - - private void ConvertAndPublishNext(object? value) - { - _value = BindingValue.FromUntyped(value); - - if (_isActive) - { - PublishNext(); - } - } - - private class Inner : IObserver + public override void Unsubscribe() { - private readonly PropertySetterBindingInstance _owner; - public Inner(PropertySetterBindingInstance owner) => _owner = owner; - public void OnCompleted() => _owner.PublishCompleted(); - public void OnError(Exception error) => _owner.PublishError(error); - public void OnNext(object? value) => _owner.ConvertAndPublishNext(value); + _twoWaySubscription?.Dispose(); + base.Unsubscribe(); } } } diff --git a/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs b/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs index 0f6efef1be..465dc21b57 100644 --- a/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs +++ b/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs @@ -1,127 +1,34 @@ using System; -using Avalonia.Data; -using Avalonia.Reactive; - -#nullable enable +using Avalonia.PropertyStore; namespace Avalonia.Styling { - /// - /// A which has been instanced on a control and whose value is lazily - /// evaluated. - /// - /// The target property type. - internal class PropertySetterTemplateInstance : SingleSubscriberObservableBase>, - ISetterInstance + internal class PropertySetterTemplateInstance : IValueEntry, ISetterInstance { - private readonly IStyleable _target; - private readonly StyledPropertyBase? _styledProperty; - private readonly DirectPropertyBase? _directProperty; private readonly ITemplate _template; - private BindingValue _value; - private IDisposable? _subscription; - private bool _isActive; - - public PropertySetterTemplateInstance( - IStyleable target, - StyledPropertyBase property, - ITemplate template) - { - _target = target; - _styledProperty = property; - _template = template; - } + private object? _value; - public PropertySetterTemplateInstance( - IStyleable target, - DirectPropertyBase property, - ITemplate template) + public PropertySetterTemplateInstance(AvaloniaProperty property, ITemplate template) { - _target = target; - _directProperty = property; _template = template; + Property = property; } - public void Start(bool hasActivator) - { - _isActive = !hasActivator; - - if (_styledProperty is not null) - { - var priority = hasActivator ? BindingPriority.StyleTrigger : BindingPriority.Style; - _subscription = _target.Bind(_styledProperty, this, priority); - } - else - { - _subscription = _target.Bind(_directProperty!, this); - } - } + public bool HasValue => true; + public AvaloniaProperty Property { get; } - public void Activate() + public object? GetValue() { - if (!_isActive) - { - _isActive = true; - PublishNext(); - } + TryGetValue(out var value); + return value; } - public void Deactivate() + public bool TryGetValue(out object? value) { - if (_isActive) - { - _isActive = false; - PublishNext(); - } + value = _value ??= _template.Build(); + return value != AvaloniaProperty.UnsetValue; } - public override void Dispose() - { - if (_subscription is not null) - { - var sub = _subscription; - _subscription = null; - sub.Dispose(); - } - else if (_isActive) - { - if (_styledProperty is not null) - { - _target.ClearValue(_styledProperty); - } - else - { - _target.ClearValue(_directProperty!); - } - } - - base.Dispose(); - } - - protected override void Subscribed() => PublishNext(); - protected override void Unsubscribed() { } - - private void EnsureTemplate() - { - if (_value.HasValue) - { - return; - } - - _value = (T) _template.Build(); - } - - private void PublishNext() - { - if (_isActive) - { - EnsureTemplate(); - PublishNext(_value); - } - else - { - PublishNext(default); - } - } + void IValueEntry.Unsubscribe() { } } } diff --git a/src/Avalonia.Base/Styling/Setter.cs b/src/Avalonia.Base/Styling/Setter.cs index d989bb0706..fdee64a0de 100644 --- a/src/Avalonia.Base/Styling/Setter.cs +++ b/src/Avalonia.Base/Styling/Setter.cs @@ -2,8 +2,7 @@ using System; using Avalonia.Animation; using Avalonia.Data; using Avalonia.Metadata; - -#nullable enable +using Avalonia.PropertyStore; namespace Avalonia.Styling { @@ -14,9 +13,10 @@ namespace Avalonia.Styling /// A is used to set a value on a /// depending on a condition. /// - public class Setter : ISetter, IAnimationSetter + public class Setter : ISetter, IValueEntry, ISetterInstance, IAnimationSetter { private object? _value; + private DirectPropertySetterInstance? _direct; /// /// Initializes a new instance of the class. @@ -30,7 +30,7 @@ namespace Avalonia.Styling /// /// The property to set. /// The property value. - public Setter(AvaloniaProperty property, object value) + public Setter(AvaloniaProperty property, object? value) { Property = property; Value = value; @@ -57,16 +57,78 @@ namespace Avalonia.Styling } } - public ISetterInstance Instance(IStyleable target) - { - target = target ?? throw new ArgumentNullException(nameof(target)); + bool IValueEntry.HasValue => true; + AvaloniaProperty IValueEntry.Property => EnsureProperty(); + + public override string ToString() => $"Setter: {Property} = {Value}"; + + void IValueEntry.Unsubscribe() { } + ISetterInstance ISetter.Instance(IStyleInstance instance, IStyleable target) + { + if (target is not AvaloniaObject ao) + throw new InvalidOperationException("Don't know how to instance a style on this type."); if (Property is null) - { throw new InvalidOperationException("Setter.Property must be set."); + if (Property.IsDirect && instance.HasActivator) + throw new InvalidOperationException( + $"Cannot set direct property '{Property}' in '{instance.Source}' because the style has an activator."); + + if (Value is IBinding binding) + return SetBinding((StyleInstance)instance, ao, binding); + else if (Value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(Property.PropertyType)) + return new PropertySetterTemplateInstance(Property, template); + else if (!Property.IsValidValue(Value)) + throw new InvalidCastException($"Setter value '{Value}' is not a valid value for property '{Property}'."); + else if (Property.IsDirect) + return SetDirectValue(target); + else + return this; + } + + object? IValueEntry.GetValue() => Value; + + bool IValueEntry.TryGetValue(out object? value) + { + value = Value; + return true; + } + + private AvaloniaProperty EnsureProperty() + { + return Property ?? throw new InvalidOperationException("Setter.Property must be set."); + } + + private ISetterInstance SetBinding(StyleInstance instance, AvaloniaObject target, IBinding binding) + { + if (!Property!.IsDirect) + { + var i = binding.Initiate(target, Property)!; + var mode = i.Mode; + + if (mode == BindingMode.Default) + { + mode = Property!.GetMetadata(target.GetType()).DefaultBindingMode; + } + + if (mode == BindingMode.OneWay || mode == BindingMode.TwoWay) + { + return new PropertySetterBindingInstance(target, instance, Property, mode, i.Observable!); + } + + throw new NotSupportedException(); + } + else + { + target.Bind(Property, binding); + return new DirectPropertySetterBindingInstance(); } + } - return Property.CreateSetterInstance(target, Value); + private ISetterInstance SetDirectValue(IStyleable target) + { + target.SetValue(Property!, Value); + return _direct ??= new DirectPropertySetterInstance(); } } } diff --git a/src/Avalonia.Base/Styling/Style.cs b/src/Avalonia.Base/Styling/Style.cs index c61b08b2a1..22c7221c45 100644 --- a/src/Avalonia.Base/Styling/Style.cs +++ b/src/Avalonia.Base/Styling/Style.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.PropertyStore; namespace Avalonia.Styling { @@ -7,6 +8,7 @@ namespace Avalonia.Styling /// public class Style : StyleBase { + private bool? _inControlTheme; private Selector? _selector; /// @@ -48,7 +50,9 @@ namespace Avalonia.Styling SelectorMatch.NeverThisInstance); if (match.IsMatch) + { Attach(target, match.Activator); + } result = match.Result; } @@ -95,6 +99,28 @@ namespace Avalonia.Styling base.SetParent(parent); } + private bool IsInControlTheme() + { + if (_inControlTheme.HasValue) + return _inControlTheme.Value; + + StyleBase? s = this; + + while (s is not null) + { + if (s is ControlTheme) + { + _inControlTheme = true; + return true; + } + + s = s.Parent as StyleBase; + } + + _inControlTheme = false; + return false; + } + private static Selector? ValidateSelector(Selector? selector) { if (selector is TemplateSelector) diff --git a/src/Avalonia.Base/Styling/StyleBase.cs b/src/Avalonia.Base/Styling/StyleBase.cs index 306a4cf010..a6eb938d5b 100644 --- a/src/Avalonia.Base/Styling/StyleBase.cs +++ b/src/Avalonia.Base/Styling/StyleBase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Metadata; +using Avalonia.PropertyStore; using Avalonia.Styling.Activators; namespace Avalonia.Styling @@ -80,11 +81,24 @@ namespace Avalonia.Styling return _resources?.TryGetResource(key, out result) ?? false; } - internal void Attach(IStyleable target, IStyleActivator? activator) + internal IValueFrame Attach(IStyleable target, IStyleActivator? activator) { - var instance = new StyleInstance(this, target, _setters, _animations, activator); - target.StyleApplied(instance); - instance.Start(); + if (target is not AvaloniaObject ao) + throw new InvalidOperationException("Styles can only be applied to AvaloniaObjects."); + + var instance = new StyleInstance(this, activator); + + if (_setters is object) + { + foreach (var setter in _setters) + { + var setterInstance = setter.Instance(instance, target); + instance.Add(setterInstance); + } + } + + ao.GetValueStore().AddFrame(instance); + return instance; } internal SelectorMatchResult TryAttachChildren(IStyleable target, object? host) diff --git a/src/Avalonia.Base/Styling/StyleInstance.cs b/src/Avalonia.Base/Styling/StyleInstance.cs index db96da6821..e3be01b33d 100644 --- a/src/Avalonia.Base/Styling/StyleInstance.cs +++ b/src/Avalonia.Base/Styling/StyleInstance.cs @@ -1,137 +1,74 @@ using System; using System.Collections.Generic; -using System.Reactive.Subjects; -using Avalonia.Animation; +using Avalonia.Data; +using Avalonia.PropertyStore; using Avalonia.Styling.Activators; -#nullable enable - namespace Avalonia.Styling { /// - /// A which has been instanced on a control. + /// Stores state for a that has been instanced on a control. /// - internal sealed class StyleInstance : IStyleInstance, IStyleActivatorSink + /// + /// implements the interface meaning that + /// it is injected directly into the value store of an . Depending + /// on the setters present on the style, it may be possible to share a single style instance + /// among all controls that the style is applied to; meaning that a single style instance can + /// apply to multiple controls. + /// + internal class StyleInstance : ValueFrameBase, IStyleInstance, IStyleActivatorSink, IDisposable { - private readonly ISetterInstance[]? _setters; - private readonly IDisposable[]? _animations; private readonly IStyleActivator? _activator; - private readonly Subject? _animationTrigger; + private List? _setters; + private bool _isActivatorInitializing; + private bool _isActivatorSubscribed; - public StyleInstance( - IStyle source, - IStyleable target, - IReadOnlyList? setters, - IReadOnlyList? animations, - IStyleActivator? activator = null) + public StyleInstance(IStyle style, IStyleActivator? activator) { - Source = source ?? throw new ArgumentNullException(nameof(source)); - Target = target ?? throw new ArgumentNullException(nameof(target)); _activator = activator; - IsActive = _activator is null; - - if (setters is not null) - { - var setterCount = setters.Count; - - _setters = new ISetterInstance[setterCount]; + Priority = activator is object ? BindingPriority.StyleTrigger : BindingPriority.Style; + Source = style; + } - for (var i = 0; i < setterCount; ++i) - { - _setters[i] = setters[i].Instance(Target); - } - } + public bool HasActivator => _activator is object; - if (animations is not null && target is Animatable animatable) + public override bool IsActive + { + get { - var animationsCount = animations.Count; - - _animations = new IDisposable[animationsCount]; - _animationTrigger = new Subject(); - - for (var i = 0; i < animationsCount; ++i) + if (_activator is object && !_isActivatorSubscribed) { - _animations[i] = animations[i].Apply(animatable, null, _animationTrigger); + _isActivatorInitializing = true; + _activator.Subscribe(this); + _isActivatorInitializing = false; + _isActivatorSubscribed = true; } + + return _activator?.IsActive ?? true; } } - public bool HasActivator => _activator is not null; - public bool IsActive { get; private set; } + public override BindingPriority Priority { get; } public IStyle Source { get; } - public IStyleable Target { get; } - public void Start() + public void Add(ISetterInstance instance) { - var hasActivator = HasActivator; - - if (_setters is not null) - { - foreach (var setter in _setters) - { - setter.Start(hasActivator); - } - } - - if (hasActivator) - { - _activator!.Subscribe(this, 0); - } - else if (_animationTrigger is not null) - { - _animationTrigger.OnNext(true); - } + if (instance is IValueEntry valueEntry) + base.Add(valueEntry); + else + (_setters ??= new()).Add(instance); } - public void Dispose() + public override void Dispose() { - if (_setters is not null) - { - foreach (var setter in _setters) - { - setter.Dispose(); - } - } - - if (_animations is not null) - { - foreach (var subscription in _animations) - { - subscription.Dispose(); - } - } - + base.Dispose(); _activator?.Dispose(); } - private void ActivatorChanged(bool value) + void IStyleActivatorSink.OnNext(bool value, int tag) { - if (IsActive != value) - { - IsActive = value; - - _animationTrigger?.OnNext(value); - - if (_setters is not null) - { - if (IsActive) - { - foreach (var setter in _setters) - { - setter.Activate(); - } - } - else - { - foreach (var setter in _setters) - { - setter.Deactivate(); - } - } - } - } + if (!_isActivatorInitializing) + Owner?.FrameActivationChanged(this); } - - void IStyleActivatorSink.OnNext(bool value, int tag) => ActivatorChanged(value); } } diff --git a/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs b/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs index cbe3771577..d9cb57baea 100644 --- a/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs +++ b/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs @@ -92,6 +92,8 @@ namespace Avalonia.Utilities return (0, false); } + public bool Contains(AvaloniaProperty property) => TryFindEntry(property.Id).Item2; + public bool TryGetValue(AvaloniaProperty property, [MaybeNullWhen(false)] out TValue value) { (int index, bool found) = TryFindEntry(property.Id); @@ -129,7 +131,12 @@ namespace Avalonia.Utilities public void SetValue(AvaloniaProperty property, TValue value) { - _entries[TryFindEntry(property.Id).Item1].Value = value; + var (index, found) = TryFindEntry(property.Id); + + if (found) + _entries[index].Value = value; + else + AddValue(property, value); } public void Remove(AvaloniaProperty property) diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs deleted file mode 100644 index bf29e0b0ac..0000000000 --- a/src/Avalonia.Base/ValueStore.cs +++ /dev/null @@ -1,507 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Avalonia.Data; -using Avalonia.PropertyStore; -using Avalonia.Utilities; - -namespace Avalonia -{ - /// - /// Stores styled property values for an . - /// - /// - /// At its core this class consists of an to - /// mapping which holds the current values for each set property. This - /// can be in one of 4 states: - /// - /// - For a single local value it will be an instance of . - /// - For a single value of a priority other than LocalValue it will be an instance of - /// ` - /// - For a single binding it will be an instance of - /// - For all other cases it will be an instance of - /// - internal class ValueStore - { - private readonly AvaloniaObject _owner; - private readonly AvaloniaPropertyValueStore _values; - private BatchUpdate? _batchUpdate; - - public ValueStore(AvaloniaObject owner) - { - _owner = owner; - _values = new AvaloniaPropertyValueStore(); - } - - public void BeginBatchUpdate() - { - _batchUpdate ??= new BatchUpdate(this); - _batchUpdate.Begin(); - } - - public void EndBatchUpdate() - { - if (_batchUpdate is null) - { - throw new InvalidOperationException("No batch update in progress."); - } - - if (_batchUpdate.End()) - { - _batchUpdate = null; - } - } - - public bool IsAnimating(AvaloniaProperty property) - { - if (TryGetValue(property, out var slot)) - { - return slot.Priority < BindingPriority.LocalValue; - } - - return false; - } - - public bool IsSet(AvaloniaProperty property) - { - if (TryGetValue(property, out var slot)) - { - return slot.GetValue().HasValue; - } - - return false; - } - - public bool TryGetValue( - StyledPropertyBase property, - BindingPriority maxPriority, - out T value) - { - if (TryGetValue(property, out var slot)) - { - var v = ((IValue)slot).GetValue(maxPriority); - - if (v.HasValue) - { - value = v.Value; - return true; - } - } - - value = default!; - return false; - } - - public IDisposable? SetValue(StyledPropertyBase property, T value, BindingPriority priority) - { - if (property.ValidateValue?.Invoke(value) == false) - { - throw new ArgumentException($"{value} is not a valid value for '{property.Name}."); - } - - IDisposable? result = null; - - if (TryGetValue(property, out var slot)) - { - result = SetExisting(slot, property, value, priority); - } - else if (property.HasCoercion) - { - // If the property has any coercion callbacks then always create a PriorityValue. - var entry = new PriorityValue(_owner, property, this); - AddValue(property, entry); - result = entry.SetValue(value, priority); - } - else - { - if (priority == BindingPriority.LocalValue) - { - AddValue(property, new LocalValueEntry(value)); - NotifyValueChanged(property, default, value, priority); - } - else - { - var entry = new ConstantValueEntry(property, value, priority, new(this)); - AddValue(property, entry); - NotifyValueChanged(property, default, value, priority); - result = entry; - } - } - - return result; - } - - public IDisposable AddBinding( - StyledPropertyBase property, - IObservable> source, - BindingPriority priority) - { - if (TryGetValue(property, out var slot)) - { - return BindExisting(slot, property, source, priority); - } - else if (property.HasCoercion) - { - // If the property has any coercion callbacks then always create a PriorityValue. - var entry = new PriorityValue(_owner, property, this); - var binding = entry.AddBinding(source, priority); - AddValue(property, entry); - return binding; - } - else - { - var entry = new BindingEntry(_owner, property, source, priority, new(this)); - AddValue(property, entry); - return entry; - } - } - - public void ClearLocalValue(StyledPropertyBase property) - { - if (TryGetValue(property, out var slot)) - { - if (slot is PriorityValue p) - { - p.ClearLocalValue(); - } - else if (slot.Priority == BindingPriority.LocalValue) - { - var old = TryGetValue(property, BindingPriority.LocalValue, out var value) ? - new Optional(value) : default; - - // During batch update values can't be removed immediately because they're needed to raise - // a correctly-typed _sink.ValueChanged notification. They instead mark themselves for removal - // by setting their priority to Unset. - if (!IsBatchUpdating()) - { - _values.Remove(property); - } - else if (slot is IDisposable d) - { - d.Dispose(); - } - else - { - // Local value entries are optimized and contain only a single value field to save space, - // so there's no way to mark them for removal at the end of a batch update. Instead convert - // them to a constant value entry with Unset priority in the event of a local value being - // cleared during a batch update. - var sentinel = new ConstantValueEntry(property, Optional.Empty, BindingPriority.Unset, new(this)); - _values.SetValue(property, sentinel); - } - - NotifyValueChanged(property, old, default, BindingPriority.Unset); - } - } - } - - public void CoerceValue(AvaloniaProperty property) - { - if (TryGetValue(property, out var slot)) - { - if (slot is IPriorityValue p) - { - p.UpdateEffectiveValue(); - } - } - } - - public Diagnostics.AvaloniaPropertyValue? GetDiagnostic(AvaloniaProperty property) - { - if (TryGetValue(property, out var slot)) - { - var slotValue = slot.GetValue(); - return new Diagnostics.AvaloniaPropertyValue( - property, - slotValue.HasValue ? slotValue.Value : AvaloniaProperty.UnsetValue, - slot.Priority, - null); - } - - return null; - } - - public void ValueChanged(AvaloniaPropertyChangedEventArgs change) - { - if (_batchUpdate is object) - { - if (change.IsEffectiveValueChange) - { - NotifyValueChanged(change.Property, change.OldValue, change.NewValue, change.Priority); - } - } - else - { - _owner.ValueChanged(change); - } - } - - public void Completed( - StyledPropertyBase property, - IPriorityValueEntry entry, - Optional oldValue) - { - // We need to include remove sentinels here so call `_values.TryGetValue` directly. - if (_values.TryGetValue(property, out var slot) && slot == entry) - { - if (_batchUpdate is null) - { - _values.Remove(property); - _owner.Completed(property, entry, oldValue); - } - else - { - _batchUpdate.ValueChanged(property, oldValue.ToObject()); - } - } - } - - private IDisposable? SetExisting( - object slot, - StyledPropertyBase property, - T value, - BindingPriority priority) - { - IDisposable? result = null; - - if (slot is IPriorityValueEntry e) - { - var priorityValue = new PriorityValue(_owner, property, this, e); - _values.SetValue(property, priorityValue); - result = priorityValue.SetValue(value, priority); - } - else if (slot is PriorityValue p) - { - result = p.SetValue(value, priority); - } - else if (slot is LocalValueEntry l) - { - if (priority == BindingPriority.LocalValue) - { - var old = l.GetValue(BindingPriority.LocalValue); - l.SetValue(value); - NotifyValueChanged(property, old, value, priority); - } - else - { - var priorityValue = new PriorityValue(_owner, property, this, l); - if (IsBatchUpdating()) - priorityValue.BeginBatchUpdate(); - result = priorityValue.SetValue(value, priority); - _values.SetValue(property, priorityValue); - } - } - else - { - throw new NotSupportedException("Unrecognised value store slot type."); - } - - return result; - } - - private IDisposable BindExisting( - object slot, - StyledPropertyBase property, - IObservable> source, - BindingPriority priority) - { - PriorityValue priorityValue; - - if (slot is IPriorityValueEntry e) - { - priorityValue = new PriorityValue(_owner, property, this, e); - - if (IsBatchUpdating()) - { - priorityValue.BeginBatchUpdate(); - } - } - else if (slot is PriorityValue p) - { - priorityValue = p; - } - else if (slot is LocalValueEntry l) - { - priorityValue = new PriorityValue(_owner, property, this, l); - } - else - { - throw new NotSupportedException("Unrecognised value store slot type."); - } - - var binding = priorityValue.AddBinding(source, priority); - _values.SetValue(property, priorityValue); - priorityValue.UpdateEffectiveValue(); - return binding; - } - - private void AddValue(AvaloniaProperty property, IValue value) - { - _values.AddValue(property, value); - if (IsBatchUpdating() && value is IBatchUpdate batch) - batch.BeginBatchUpdate(); - value.Start(); - } - - private void NotifyValueChanged( - AvaloniaProperty property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority) - { - if (_batchUpdate is null) - { - _owner.ValueChanged(new AvaloniaPropertyChangedEventArgs( - _owner, - property, - oldValue, - newValue, - priority)); - } - else - { - _batchUpdate.ValueChanged(property, oldValue.ToObject()); - } - } - - private bool IsBatchUpdating() => _batchUpdate?.IsBatchUpdating == true; - - private bool TryGetValue(AvaloniaProperty property, [MaybeNullWhen(false)] out IValue value) - { - return _values.TryGetValue(property, out value) && !IsRemoveSentinel(value); - } - - private static bool IsRemoveSentinel(IValue value) - { - // Local value entries are optimized and contain only a single value field to save space, - // so there's no way to mark them for removal at the end of a batch update. Instead a - // ConstantValueEntry with a priority of Unset is used as a sentinel value. - return value is IConstantValueEntry t && t.Priority == BindingPriority.Unset; - } - - private class BatchUpdate - { - private ValueStore _owner; - private List? _notifications; - private int _batchUpdateCount; - private int _iterator = -1; - - public BatchUpdate(ValueStore owner) => _owner = owner; - - public bool IsBatchUpdating => _batchUpdateCount > 0; - - public void Begin() - { - if (_batchUpdateCount++ == 0) - { - var values = _owner._values; - - for (var i = 0; i < values.Count; ++i) - { - (values[i] as IBatchUpdate)?.BeginBatchUpdate(); - } - } - } - - public bool End() - { - if (--_batchUpdateCount > 0) - return false; - - var values = _owner._values; - - // First call EndBatchUpdate on all bindings. This should cause the active binding to be subscribed - // but notifications will still not be raised because the owner ValueStore will still have a reference - // to this batch update object. - for (var i = 0; i < values.Count; ++i) - { - (values[i] as IBatchUpdate)?.EndBatchUpdate(); - - // Somehow subscribing to a binding caused a new batch update. This shouldn't happen but in case it - // does, abort and continue batch updating. - if (_batchUpdateCount > 0) - return false; - } - - if (_notifications is object) - { - // Raise all batched notifications. Doing this can cause other notifications to be added and even - // cause a new batch update to start, so we need to handle _notifications being modified by storing - // the index in field. - _iterator = 0; - - for (; _iterator < _notifications.Count; ++_iterator) - { - var entry = _notifications[_iterator]; - - if (values.TryGetValue(entry.property, out var slot)) - { - var oldValue = entry.oldValue; - var newValue = slot.GetValue(); - - // Raising this notification can cause a new batch update to be started, which in turn - // results in another change to the property. In this case we need to update the old value - // so that the *next* notification has an oldValue which follows on from the newValue - // raised here. - _notifications[_iterator] = new Notification - { - property = entry.property, - oldValue = newValue, - }; - - // Call _sink.ValueChanged with an appropriately typed AvaloniaPropertyChangedEventArgs. - slot.RaiseValueChanged(_owner._owner, entry.property, oldValue, newValue); - - // During batch update values can't be removed immediately because they're needed to raise - // the _sink.ValueChanged notification. They instead mark themselves for removal by setting - // their priority to Unset. We need to re-read the slot here because raising ValueChanged - // could have caused it to be updated. - if (values.TryGetValue(entry.property, out var updatedSlot) && - updatedSlot.Priority == BindingPriority.Unset) - { - values.Remove(entry.property); - } - } - - // If a new batch update was started while ending this one, abort. - if (_batchUpdateCount > 0) - return false; - } - } - - _iterator = int.MaxValue - 1; - return true; - } - - public void ValueChanged(AvaloniaProperty property, Optional oldValue) - { - _notifications ??= new List(); - - for (var i = 0; i < _notifications.Count; ++i) - { - if (_notifications[i].property == property) - { - oldValue = _notifications[i].oldValue; - _notifications.RemoveAt(i); - - if (i <= _iterator) - --_iterator; - break; - } - } - - _notifications.Add(new Notification - { - property = property, - oldValue = oldValue, - }); - } - - private struct Notification - { - public AvaloniaProperty property; - public Optional oldValue; - } - } - } -} diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 8feba116f0..da3de890d8 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -552,27 +552,24 @@ namespace Avalonia BindingPriority.LocalValue); } - protected internal sealed override void LogBindingError(AvaloniaProperty property, Exception e) + internal override ParametrizedLogger? GetBindingWarningLogger( + AvaloniaProperty property, + Exception? e) { - // Don't log a binding error unless the control is attached to a logical tree. - if (((ILogical)this).IsAttachedToLogicalTree) - { - if (e is BindingChainException b && - string.IsNullOrEmpty(b.ExpressionErrorPoint) && - DataContext == null) - { - // The error occurred at the root of the binding chain and DataContext is null; - // don't log this - the DataContext probably hasn't been set up yet. - return; - } + // Don't log a binding error unless the control is attached to the logical tree. + if (!((ILogical)this).IsAttachedToLogicalTree) + return null; - Logger.TryGet(LogEventLevel.Warning, LogArea.Binding)?.Log( - this, - "Error in binding to {Target}.{Property}: {Message}", - this, - property, - e.Message); + if (e is BindingChainException b && + string.IsNullOrEmpty(b.ExpressionErrorPoint) && + DataContext == null) + { + // The error occurred at the root of the binding chain and DataContext is null; + // don't log this - the DataContext probably hasn't been set up yet. + return null; } + + return Logger.TryGet(LogEventLevel.Warning, LogArea.Binding); } /// diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index dc52cc3ae2..d815c32070 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -371,11 +371,11 @@ namespace Avalonia.Controls.Primitives { base.OnPropertyChanged(change); - if (change.Property == ThemeProperty) - { - foreach (var child in this.GetTemplateChildren()) - child.InvalidateStyles(); - } + //if (change.Property == ThemeProperty) + //{ + // foreach (var child in this.GetTemplateChildren()) + // child.InvalidateStyles(); + //} } /// diff --git a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs index f9ff8caab1..294c830c91 100644 --- a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs +++ b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs @@ -413,25 +413,33 @@ namespace Avalonia.Base.UnitTests.Animation } [Fact] - public void Transitions_Can_Re_Set_During_Batch_Update() + public void Transitions_Can_Re_Set_During_Styling() { var target = CreateTarget(); var control = CreateControl(target.Object); // Assigning and then clearing Transitions ensures we have a transition state // collection created. - control.Transitions = null; + control.ClearValue(Control.TransitionsProperty); - control.BeginBatchUpdate(); + control.GetValueStore().BeginStyling(); // Setting opacity then Transitions means that we receive the Transitions change - // after the Opacity change when EndBatchUpdate is called. - control.Opacity = 0.5; - control.Transitions = new Transitions { target.Object }; + // after the Opacity change when EndStyling is called. + var style = new Style + { + Setters = + { + new Setter(Control.OpacityProperty, 0.5), + new Setter(Control.TransitionsProperty, new Transitions { target.Object }), + } + }; + + style.TryAttach(control, control); // Which means that the transition state hasn't been initialized with the new // Transitions when the Opacity change notification gets raised here. - control.EndBatchUpdate(); + control.GetValueStore().EndStyling(); } private static IDisposable Start() diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_BatchUpdate.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_BatchUpdate.cs deleted file mode 100644 index 45de860894..0000000000 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_BatchUpdate.cs +++ /dev/null @@ -1,695 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Text; -using Avalonia.Data; -using Avalonia.Layout; -using Xunit; - -namespace Avalonia.Base.UnitTests -{ - public class AvaloniaObjectTests_BatchUpdate - { - [Fact] - public void SetValue_Should_Not_Raise_Property_Changes_During_Batch_Update() - { - var target = new TestClass(); - var raised = new List(); - - target.GetObservable(TestClass.FooProperty).Skip(1).Subscribe(x => raised.Add(x)); - target.BeginBatchUpdate(); - target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue); - - Assert.Empty(raised); - } - - [Fact] - public void Binding_Should_Not_Raise_Property_Changes_During_Batch_Update() - { - var target = new TestClass(); - var observable = new TestObservable("foo"); - var raised = new List(); - - target.GetObservable(TestClass.FooProperty).Skip(1).Subscribe(x => raised.Add(x)); - target.BeginBatchUpdate(); - target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue); - - Assert.Empty(raised); - } - - [Fact] - public void Binding_Completion_Should_Not_Raise_Property_Changes_During_Batch_Update() - { - var target = new TestClass(); - var observable = new TestObservable("foo"); - var raised = new List(); - - target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue); - target.GetObservable(TestClass.FooProperty).Skip(1).Subscribe(x => raised.Add(x)); - target.BeginBatchUpdate(); - observable.OnCompleted(); - - Assert.Empty(raised); - } - - [Fact] - public void Binding_Disposal_Should_Not_Raise_Property_Changes_During_Batch_Update() - { - var target = new TestClass(); - var observable = new TestObservable("foo"); - var raised = new List(); - - var sub = target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue); - target.GetObservable(TestClass.FooProperty).Skip(1).Subscribe(x => raised.Add(x)); - target.BeginBatchUpdate(); - sub.Dispose(); - - Assert.Empty(raised); - } - - [Fact] - public void SetValue_Change_Should_Be_Raised_After_Batch_Update_1() - { - var target = new TestClass(); - var raised = new List(); - - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue); - target.EndBatchUpdate(); - - Assert.Equal(1, raised.Count); - Assert.Equal("foo", target.Foo); - Assert.Null(raised[0].OldValue); - Assert.Equal("foo", raised[0].NewValue); - } - - [Fact] - public void SetValue_Change_Should_Be_Raised_After_Batch_Update_2() - { - var target = new TestClass(); - var raised = new List(); - - target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue); - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - target.SetValue(TestClass.FooProperty, "bar", BindingPriority.LocalValue); - target.SetValue(TestClass.FooProperty, "baz", BindingPriority.LocalValue); - target.EndBatchUpdate(); - - Assert.Equal(1, raised.Count); - Assert.Equal("baz", target.Foo); - } - - [Fact] - public void SetValue_Change_Should_Be_Raised_After_Batch_Update_3() - { - var target = new TestClass(); - var raised = new List(); - - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - target.SetValue(TestClass.BazProperty, Orientation.Horizontal, BindingPriority.LocalValue); - target.EndBatchUpdate(); - - Assert.Equal(1, raised.Count); - Assert.Equal(TestClass.BazProperty, raised[0].Property); - Assert.Equal(Orientation.Vertical, raised[0].OldValue); - Assert.Equal(Orientation.Horizontal, raised[0].NewValue); - Assert.Equal(Orientation.Horizontal, target.Baz); - } - - [Fact] - public void SetValue_Changes_Should_Be_Raised_In_Correct_Order_After_Batch_Update() - { - var target = new TestClass(); - var raised = new List(); - - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue); - target.SetValue(TestClass.BarProperty, "bar", BindingPriority.LocalValue); - target.SetValue(TestClass.FooProperty, "baz", BindingPriority.LocalValue); - target.EndBatchUpdate(); - - Assert.Equal(2, raised.Count); - Assert.Equal(TestClass.BarProperty, raised[0].Property); - Assert.Equal(TestClass.FooProperty, raised[1].Property); - Assert.Equal("baz", target.Foo); - Assert.Equal("bar", target.Bar); - } - - [Fact] - public void SetValue_And_Binding_Changes_Should_Be_Raised_In_Correct_Order_After_Batch_Update_1() - { - var target = new TestClass(); - var observable = new TestObservable("baz"); - var raised = new List(); - - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue); - target.SetValue(TestClass.BarProperty, "bar", BindingPriority.LocalValue); - target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue); - target.EndBatchUpdate(); - - Assert.Equal(2, raised.Count); - Assert.Equal(TestClass.BarProperty, raised[0].Property); - Assert.Equal(TestClass.FooProperty, raised[1].Property); - Assert.Equal("baz", target.Foo); - Assert.Equal("bar", target.Bar); - } - - [Fact] - public void SetValue_And_Binding_Changes_Should_Be_Raised_In_Correct_Order_After_Batch_Update_2() - { - var target = new TestClass(); - var observable = new TestObservable("foo"); - var raised = new List(); - - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue); - target.SetValue(TestClass.BarProperty, "bar", BindingPriority.LocalValue); - target.SetValue(TestClass.FooProperty, "baz", BindingPriority.LocalValue); - target.EndBatchUpdate(); - - Assert.Equal(2, raised.Count); - Assert.Equal(TestClass.BarProperty, raised[0].Property); - Assert.Equal(TestClass.FooProperty, raised[1].Property); - Assert.Equal("baz", target.Foo); - Assert.Equal("bar", target.Bar); - } - - [Fact] - public void SetValue_And_Binding_Changes_Should_Be_Raised_In_Correct_Order_After_Batch_Update_3() - { - var target = new TestClass(); - var observable1 = new TestObservable("foo"); - var observable2 = new TestObservable("qux"); - var raised = new List(); - - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - target.Bind(TestClass.FooProperty, observable2, BindingPriority.LocalValue); - target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue); - target.SetValue(TestClass.BarProperty, "bar", BindingPriority.LocalValue); - target.SetValue(TestClass.FooProperty, "baz", BindingPriority.LocalValue); - target.EndBatchUpdate(); - - Assert.Equal(2, raised.Count); - Assert.Equal(TestClass.BarProperty, raised[0].Property); - Assert.Equal(TestClass.FooProperty, raised[1].Property); - Assert.Equal("baz", target.Foo); - Assert.Equal("bar", target.Bar); - } - - [Fact] - public void Binding_Change_Should_Be_Raised_After_Batch_Update_1() - { - var target = new TestClass(); - var observable = new TestObservable("foo"); - var raised = new List(); - - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue); - target.EndBatchUpdate(); - - Assert.Equal(1, raised.Count); - Assert.Equal("foo", target.Foo); - Assert.Null(raised[0].OldValue); - Assert.Equal("foo", raised[0].NewValue); - } - - [Fact] - public void Binding_Change_Should_Be_Raised_After_Batch_Update_2() - { - var target = new TestClass(); - var observable1 = new TestObservable("bar"); - var observable2 = new TestObservable("baz"); - var raised = new List(); - - target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue); - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue); - target.Bind(TestClass.FooProperty, observable2, BindingPriority.LocalValue); - target.EndBatchUpdate(); - - Assert.Equal(1, raised.Count); - Assert.Equal("baz", target.Foo); - Assert.Equal("foo", raised[0].OldValue); - Assert.Equal("baz", raised[0].NewValue); - } - - [Fact] - public void Binding_Change_Should_Be_Raised_After_Batch_Update_3() - { - var target = new TestClass(); - var observable = new TestObservable(Orientation.Horizontal); - var raised = new List(); - - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - target.Bind(TestClass.BazProperty, observable, BindingPriority.LocalValue); - target.EndBatchUpdate(); - - Assert.Equal(1, raised.Count); - Assert.Equal(TestClass.BazProperty, raised[0].Property); - Assert.Equal(Orientation.Vertical, raised[0].OldValue); - Assert.Equal(Orientation.Horizontal, raised[0].NewValue); - Assert.Equal(Orientation.Horizontal, target.Baz); - } - - [Fact] - public void Binding_Completion_Should_Be_Raised_After_Batch_Update() - { - var target = new TestClass(); - var observable = new TestObservable("foo"); - var raised = new List(); - - target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue); - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - observable.OnCompleted(); - target.EndBatchUpdate(); - - Assert.Equal(1, raised.Count); - Assert.Null(target.Foo); - Assert.Equal("foo", raised[0].OldValue); - Assert.Null(raised[0].NewValue); - Assert.Equal(BindingPriority.Unset, raised[0].Priority); - } - - [Fact] - public void Binding_Disposal_Should_Be_Raised_After_Batch_Update() - { - var target = new TestClass(); - var observable = new TestObservable("foo"); - var raised = new List(); - - var sub = target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue); - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - sub.Dispose(); - target.EndBatchUpdate(); - - Assert.Equal(1, raised.Count); - Assert.Null(target.Foo); - Assert.Equal("foo", raised[0].OldValue); - Assert.Null(raised[0].NewValue); - Assert.Equal(BindingPriority.Unset, raised[0].Priority); - } - - [Fact] - public void ClearValue_Change_Should_Be_Raised_After_Batch_Update_1() - { - var target = new TestClass(); - var raised = new List(); - - target.Foo = "foo"; - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - target.ClearValue(TestClass.FooProperty); - target.EndBatchUpdate(); - - Assert.Equal(1, raised.Count); - Assert.Null(target.Foo); - Assert.Equal("foo", raised[0].OldValue); - Assert.Null(raised[0].NewValue); - Assert.Equal(BindingPriority.Unset, raised[0].Priority); - } - - [Fact] - public void Bindings_Should_Be_Subscribed_Before_Batch_Update() - { - var target = new TestClass(); - var observable1 = new TestObservable("foo"); - var observable2 = new TestObservable("bar"); - - target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue); - target.Bind(TestClass.FooProperty, observable2, BindingPriority.LocalValue); - - Assert.Equal(1, observable1.SubscribeCount); - Assert.Equal(1, observable2.SubscribeCount); - } - - [Fact] - public void Non_Active_Binding_Should_Not_Be_Subscribed_Before_Batch_Update() - { - var target = new TestClass(); - var observable1 = new TestObservable("foo"); - var observable2 = new TestObservable("bar"); - - target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue); - target.Bind(TestClass.FooProperty, observable2, BindingPriority.Style); - - Assert.Equal(1, observable1.SubscribeCount); - Assert.Equal(0, observable2.SubscribeCount); - } - - [Fact] - public void LocalValue_Bindings_Should_Be_Subscribed_During_Batch_Update() - { - var target = new TestClass(); - var observable1 = new TestObservable("foo"); - var observable2 = new TestObservable("bar"); - var raised = new List(); - - target.PropertyChanged += (s, e) => raised.Add(e); - - // We need to subscribe to LocalValue bindings even if we've got a batch operation - // in progress because otherwise we don't know whether the binding or a subsequent - // SetValue with local priority will win. Notifications however shouldn't be sent. - target.BeginBatchUpdate(); - target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue); - target.Bind(TestClass.FooProperty, observable2, BindingPriority.LocalValue); - - Assert.Equal(1, observable1.SubscribeCount); - Assert.Equal(1, observable2.SubscribeCount); - Assert.Empty(raised); - } - - [Fact] - public void Style_Bindings_Should_Not_Be_Subscribed_During_Batch_Update() - { - var target = new TestClass(); - var observable1 = new TestObservable("foo"); - var observable2 = new TestObservable("bar"); - - target.BeginBatchUpdate(); - target.Bind(TestClass.FooProperty, observable1, BindingPriority.Style); - target.Bind(TestClass.FooProperty, observable2, BindingPriority.StyleTrigger); - - Assert.Equal(0, observable1.SubscribeCount); - Assert.Equal(0, observable2.SubscribeCount); - } - - [Fact] - public void Active_Style_Binding_Should_Be_Subscribed_After_Batch_Uppdate_1() - { - var target = new TestClass(); - var observable1 = new TestObservable("foo"); - var observable2 = new TestObservable("bar"); - - target.BeginBatchUpdate(); - target.Bind(TestClass.FooProperty, observable1, BindingPriority.Style); - target.Bind(TestClass.FooProperty, observable2, BindingPriority.Style); - target.EndBatchUpdate(); - - Assert.Equal(0, observable1.SubscribeCount); - Assert.Equal(1, observable2.SubscribeCount); - } - - [Fact] - public void Active_Style_Binding_Should_Be_Subscribed_After_Batch_Uppdate_2() - { - var target = new TestClass(); - var observable1 = new TestObservable("foo"); - var observable2 = new TestObservable("bar"); - - target.BeginBatchUpdate(); - target.Bind(TestClass.FooProperty, observable1, BindingPriority.StyleTrigger); - target.Bind(TestClass.FooProperty, observable2, BindingPriority.Style); - target.EndBatchUpdate(); - - Assert.Equal(1, observable1.SubscribeCount); - Assert.Equal(0, observable2.SubscribeCount); - } - - [Fact] - public void Change_Can_Be_Triggered_By_Ending_Batch_Update_1() - { - var target = new TestClass(); - var raised = new List(); - - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - target.Foo = "foo"; - - target.PropertyChanged += (s, e) => - { - if (e.Property == TestClass.FooProperty && (string)e.NewValue == "foo") - target.Bar = "bar"; - }; - - target.EndBatchUpdate(); - - Assert.Equal("foo", target.Foo); - Assert.Equal("bar", target.Bar); - Assert.Equal(2, raised.Count); - Assert.Equal(TestClass.FooProperty, raised[0].Property); - Assert.Equal(TestClass.BarProperty, raised[1].Property); - } - - [Fact] - public void Change_Can_Be_Triggered_By_Ending_Batch_Update_2() - { - var target = new TestClass(); - var raised = new List(); - - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - target.Foo = "foo"; - target.Bar = "baz"; - - target.PropertyChanged += (s, e) => - { - if (e.Property == TestClass.FooProperty && (string)e.NewValue == "foo") - target.Bar = "bar"; - }; - - target.EndBatchUpdate(); - - Assert.Equal("foo", target.Foo); - Assert.Equal("bar", target.Bar); - Assert.Equal(2, raised.Count); - } - - [Fact] - public void Batch_Update_Can_Be_Triggered_By_Ending_Batch_Update() - { - var target = new TestClass(); - var raised = new List(); - - target.PropertyChanged += (s, e) => raised.Add(e); - - target.BeginBatchUpdate(); - target.Foo = "foo"; - target.Bar = "baz"; - - // Simulates the following scenario: - // - A control is added to the logical tree - // - A batch update is started to apply styles - // - Ending the batch update triggers something which removes the control from the logical tree - // - A new batch update is started to detach styles - target.PropertyChanged += (s, e) => - { - if (e.Property == TestClass.FooProperty && (string)e.NewValue == "foo") - { - target.BeginBatchUpdate(); - target.ClearValue(TestClass.FooProperty); - target.ClearValue(TestClass.BarProperty); - target.EndBatchUpdate(); - } - }; - - target.EndBatchUpdate(); - - Assert.Null(target.Foo); - Assert.Null(target.Bar); - Assert.Equal(2, raised.Count); - Assert.Equal(TestClass.FooProperty, raised[0].Property); - Assert.Null(raised[0].OldValue); - Assert.Equal("foo", raised[0].NewValue); - Assert.Equal(TestClass.FooProperty, raised[1].Property); - Assert.Equal("foo", raised[1].OldValue); - Assert.Null(raised[1].NewValue); - } - - [Fact] - public void Can_Set_Cleared_Value_When_Ending_Batch_Update() - { - var target = new TestClass(); - var raised = 0; - - target.Foo = "foo"; - - target.BeginBatchUpdate(); - target.ClearValue(TestClass.FooProperty); - target.PropertyChanged += (sender, e) => - { - if (e.Property == TestClass.FooProperty && e.NewValue is null) - { - target.Foo = "bar"; - ++raised; - } - }; - target.EndBatchUpdate(); - - Assert.Equal("bar", target.Foo); - Assert.Equal(1, raised); - } - - [Fact] - public void Can_Bind_Cleared_Value_When_Ending_Batch_Update() - { - var target = new TestClass(); - var raised = 0; - var notifications = new List(); - - target.Foo = "foo"; - - target.BeginBatchUpdate(); - target.ClearValue(TestClass.FooProperty); - target.PropertyChanged += (sender, e) => - { - if (e.Property == TestClass.FooProperty && e.NewValue is null) - { - target.Bind(TestClass.FooProperty, new TestObservable("bar")); - ++raised; - } - - notifications.Add(e); - }; - target.EndBatchUpdate(); - - Assert.Equal("bar", target.Foo); - Assert.Equal(1, raised); - Assert.Equal(2, notifications.Count); - Assert.Equal(null, notifications[0].NewValue); - Assert.Equal("bar", notifications[1].NewValue); - } - - [Fact] - public void Can_Bind_Completed_Binding_Back_To_Original_Value_When_Ending_Batch_Update() - { - var target = new TestClass(); - var raised = 0; - var notifications = new List(); - var observable1 = new TestObservable("foo"); - var observable2 = new TestObservable("foo"); - - target.Bind(TestClass.FooProperty, observable1); - - target.BeginBatchUpdate(); - observable1.OnCompleted(); - target.PropertyChanged += (sender, e) => - { - if (e.Property == TestClass.FooProperty && e.NewValue is null) - { - target.Bind(TestClass.FooProperty, observable2); - ++raised; - } - - notifications.Add(e); - }; - target.EndBatchUpdate(); - - Assert.Equal("foo", target.Foo); - Assert.Equal(1, raised); - Assert.Equal(2, notifications.Count); - Assert.Equal(null, notifications[0].NewValue); - Assert.Equal("foo", notifications[1].NewValue); - } - - [Fact] - public void Can_Run_Empty_Batch_Update_When_Ending_Batch_Update() - { - var target = new TestClass(); - var raised = 0; - var notifications = new List(); - - target.Foo = "foo"; - target.Bar = "bar"; - - target.BeginBatchUpdate(); - target.ClearValue(TestClass.FooProperty); - target.ClearValue(TestClass.BarProperty); - target.PropertyChanged += (sender, e) => - { - if (e.Property == TestClass.BarProperty) - { - target.BeginBatchUpdate(); - target.EndBatchUpdate(); - } - - ++raised; - }; - target.EndBatchUpdate(); - - Assert.Null(target.Foo); - Assert.Null(target.Bar); - Assert.Equal(2, raised); - } - - public class TestClass : AvaloniaObject - { - public static readonly StyledProperty FooProperty = - AvaloniaProperty.Register(nameof(Foo)); - - public static readonly StyledProperty BarProperty = - AvaloniaProperty.Register(nameof(Bar)); - - public static readonly StyledProperty BazProperty = - AvaloniaProperty.Register(nameof(Bar), Orientation.Vertical); - - public string Foo - { - get => GetValue(FooProperty); - set => SetValue(FooProperty, value); - } - - public string Bar - { - get => GetValue(BarProperty); - set => SetValue(BarProperty, value); - } - - public Orientation Baz - { - get => GetValue(BazProperty); - set => SetValue(BazProperty, value); - } - } - - public class TestObservable : ObservableBase> - { - private readonly T _value; - private IObserver> _observer; - - public TestObservable(T value) => _value = value; - - public int SubscribeCount { get; private set; } - - public void OnCompleted() => _observer.OnCompleted(); - public void OnError(Exception e) => _observer.OnError(e); - - protected override IDisposable SubscribeCore(IObserver> observer) - { - ++SubscribeCount; - _observer = observer; - observer.OnNext(_value); - return Disposable.Empty; - } - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 6b4a6f89df..35b8c4c78f 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -4,18 +4,18 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading; using System.Threading.Tasks; - using Avalonia.Controls; using Avalonia.Data; using Avalonia.Logging; using Avalonia.Platform; using Avalonia.Threading; using Avalonia.UnitTests; -using Avalonia.Utilities; using Microsoft.Reactive.Testing; using Moq; using Xunit; +#nullable enable + namespace Avalonia.Base.UnitTests { public class AvaloniaObjectTests_Binding @@ -24,11 +24,10 @@ namespace Avalonia.Base.UnitTests public void Bind_Sets_Current_Value() { var target = new Class1(); - var source = new Class1(); + var source = new BehaviorSubject>("initial"); var property = Class1.FooProperty; - source.SetValue(property, "initial"); - target.Bind(property, source.GetObservable(property)); + target.Bind(property, source); Assert.Equal("initial", target.GetValue(property)); } @@ -38,18 +37,21 @@ namespace Avalonia.Base.UnitTests { var target = new Class1(); var source = new Subject>(); - bool raised = false; + var raised = 0; target.PropertyChanged += (s, e) => - raised = e.Property == Class1.FooProperty && - (string)e.OldValue == "foodefault" && - (string)e.NewValue == "newvalue" && - e.Priority == BindingPriority.LocalValue; + { + Assert.Equal(Class1.FooProperty, e.Property); + Assert.Equal("foodefault", (string?)e.OldValue); + Assert.Equal("newvalue", (string?)e.NewValue); + Assert.Equal(BindingPriority.LocalValue, e.Priority); + ++raised; + }; target.Bind(Class1.FooProperty, source); source.OnNext("newvalue"); - Assert.True(raised); + Assert.Equal(1, raised); } [Fact] @@ -71,7 +73,7 @@ namespace Avalonia.Base.UnitTests public void Setting_LocalValue_Overrides_Binding_Until_Binding_Produces_Next_Value() { var target = new Class1(); - var source = new Subject(); + var source = new Subject>(); var property = Class1.FooProperty; target.Bind(property, source); @@ -81,7 +83,7 @@ namespace Avalonia.Base.UnitTests target.SetValue(property, "bar"); Assert.Equal("bar", target.GetValue(property)); - source.OnNext("baz"); + source.OnNext("baz"); Assert.Equal("baz", target.GetValue(property)); } @@ -89,7 +91,7 @@ namespace Avalonia.Base.UnitTests public void Completing_LocalValue_Binding_Reverts_To_Default_Value_Even_When_Local_Value_Set_Earlier() { var target = new Class1(); - var source = new Subject(); + var source = new Subject>(); var property = Class1.FooProperty; target.Bind(property, source); @@ -102,10 +104,10 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void Completing_LocalValue_Binding_Should_Not_Revert_To_Set_LocalValue() + public void Disposing_LocalValue_Binding_Should_Not_Revert_To_Set_LocalValue() { var target = new Class1(); - var source = new BehaviorSubject("bar"); + var source = new BehaviorSubject>("bar"); target.SetValue(Class1.FooProperty, "foo"); var sub = target.Bind(Class1.FooProperty, source); @@ -117,11 +119,43 @@ namespace Avalonia.Base.UnitTests Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); } + [Fact] + public void LocalValue_Binding_Should_Override_Style_Binding() + { + var target = new Class1(); + var source1 = new BehaviorSubject>("foo"); + var source2 = new BehaviorSubject>("bar"); + + target.Bind(Class1.FooProperty, source1, BindingPriority.Style); + + Assert.Equal("foo", target.GetValue(Class1.FooProperty)); + + target.Bind(Class1.FooProperty, source2, BindingPriority.LocalValue); + + Assert.Equal("bar", target.GetValue(Class1.FooProperty)); + } + + [Fact] + public void Style_Binding_Should_NotOverride_LocalValue_Binding() + { + var target = new Class1(); + var source1 = new BehaviorSubject>("foo"); + var source2 = new BehaviorSubject>("bar"); + + target.Bind(Class1.FooProperty, source1, BindingPriority.LocalValue); + + Assert.Equal("foo", target.GetValue(Class1.FooProperty)); + + target.Bind(Class1.FooProperty, source2, BindingPriority.Style); + + Assert.Equal("foo", target.GetValue(Class1.FooProperty)); + } + [Fact] public void Completing_Animation_Binding_Reverts_To_Set_LocalValue() { var target = new Class1(); - var source = new Subject(); + var source = new Subject>(); var property = Class1.FooProperty; target.SetValue(property, "foo"); @@ -192,7 +226,7 @@ namespace Avalonia.Base.UnitTests var property = Class1.FooProperty; var raised = 0; - target.Bind(property, new BehaviorSubject("bar"), BindingPriority.Style); + target.Bind(property, new BehaviorSubject>("bar"), BindingPriority.Style); target.Bind(property, source); Assert.Equal("foo", target.GetValue(property)); @@ -255,18 +289,18 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void Second_LocalValue_Binding_Overrides_First() + public void Second_LocalValue_Binding_Unsubscribes_First() { var property = Class1.FooProperty; var target = new Class1(); - var source1 = new Subject(); - var source2 = new Subject(); + var source1 = new Subject>(); + var source2 = new Subject>(); target.Bind(property, source1, BindingPriority.LocalValue); target.Bind(property, source2, BindingPriority.LocalValue); source1.OnNext("foo"); - Assert.Equal("foo", target.GetValue(property)); + Assert.Equal("foodefault", target.GetValue(property)); source2.OnNext("bar"); Assert.Equal("bar", target.GetValue(property)); @@ -276,12 +310,12 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void Completing_Second_LocalValue_Binding_Reverts_To_First() + public void Completing_Second_LocalValue_Binding_Doesnt_Revert_To_First() { var property = Class1.FooProperty; var target = new Class1(); - var source1 = new Subject(); - var source2 = new Subject(); + var source1 = new Subject>(); + var source2 = new Subject>(); target.Bind(property, source1, BindingPriority.LocalValue); target.Bind(property, source2, BindingPriority.LocalValue); @@ -291,7 +325,7 @@ namespace Avalonia.Base.UnitTests source1.OnNext("baz"); source2.OnCompleted(); - Assert.Equal("baz", target.GetValue(property)); + Assert.Equal("foodefault", target.GetValue(property)); } [Fact] @@ -299,8 +333,8 @@ namespace Avalonia.Base.UnitTests { var property = Class1.FooProperty; var target = new Class1(); - var source1 = new Subject(); - var source2 = new Subject(); + var source1 = new Subject>(); + var source2 = new Subject>(); target.Bind(property, source1, BindingPriority.Style); target.Bind(property, source2, BindingPriority.StyleTrigger); @@ -326,7 +360,19 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void Bind_To_ValueType_Accepts_UnsetValue() + public void Bind_NonGeneric_Can_Set_Null_On_Reference_Type() + { + var target = new Class1(); + var source = new BehaviorSubject(null); + var property = Class1.FooProperty; + + target.Bind(property, source); + + Assert.Null(target.GetValue(property)); + } + + [Fact] + public void LocalValue_Bind_NonGeneric_To_ValueType_Accepts_UnsetValue() { var target = new Class1(); var source = new Subject(); @@ -339,6 +385,46 @@ namespace Avalonia.Base.UnitTests Assert.False(target.IsSet(Class1.QuxProperty)); } + [Fact] + public void Style_Bind_NonGeneric_To_ValueType_Accepts_UnsetValue() + { + var target = new Class1(); + var source = new Subject(); + + target.Bind(Class1.QuxProperty, source, BindingPriority.Style); + source.OnNext(6.7); + source.OnNext(AvaloniaProperty.UnsetValue); + + Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); + Assert.False(target.IsSet(Class1.QuxProperty)); + } + + [Fact] + public void LocalValue_Bind_NonGeneric_To_ValueType_Accepts_DoNothing() + { + var target = new Class1(); + var source = new Subject(); + + target.Bind(Class1.QuxProperty, source); + source.OnNext(6.7); + source.OnNext(BindingOperations.DoNothing); + + Assert.Equal(6.7, target.GetValue(Class1.QuxProperty)); + } + + [Fact] + public void Style_Bind_NonGeneric_To_ValueType_Accepts_DoNothing() + { + var target = new Class1(); + var source = new Subject(); + + target.Bind(Class1.QuxProperty, source, BindingPriority.Style); + source.OnNext(6.7); + source.OnNext(BindingOperations.DoNothing); + + Assert.Equal(6.7, target.GetValue(Class1.QuxProperty)); + } + [Fact] public void OneTime_Binding_Ignores_UnsetValue() { @@ -374,7 +460,7 @@ namespace Avalonia.Base.UnitTests { Class1 target = new Class1(); - target.Bind(Class2.BarProperty, Observable.Never().StartWith("foo")); + target.Bind(Class2.BarProperty, Observable.Never>().StartWith("foo")); Assert.Equal("foo", target.GetValue(Class2.BarProperty)); } @@ -404,7 +490,7 @@ namespace Avalonia.Base.UnitTests public void Observable_Is_Unsubscribed_When_Subscription_Disposed() { var scheduler = new TestScheduler(); - var source = scheduler.CreateColdObservable(); + var source = scheduler.CreateColdObservable>(); var target = new Class1(); var subscription = target.Bind(Class1.FooProperty, source); @@ -482,7 +568,7 @@ namespace Avalonia.Base.UnitTests public void Local_Binding_Overwrites_Local_Value() { var target = new Class1(); - var binding = new Subject(); + var binding = new Subject>(); target.Bind(Class1.FooProperty, binding); @@ -660,6 +746,76 @@ namespace Avalonia.Base.UnitTests } } + [Fact] + public void Untyped_LocalValue_Binding_Logs_Invalid_Value_Type() + { + var target = new Class1(); + var source = new Subject(); + var called = false; + var expectedMessageTemplate = "Error in binding to {Target}.{Property}: expected {ExpectedType}, got {Value} ({ValueType})"; + + LogCallback checkLogMessage = (level, area, src, mt, pv) => + { + if (level == LogEventLevel.Warning && + area == LogArea.Binding && + mt == expectedMessageTemplate && + src == target && + pv[0].GetType() == typeof(Class1) && + (AvaloniaProperty)pv[1] == Class1.QuxProperty && + (Type)pv[2] == typeof(double) && + (string)pv[3] == "foo" && + (Type)pv[4] == typeof(string)) + { + called = true; + } + }; + + using (TestLogSink.Start(checkLogMessage)) + { + target.Bind(Class1.QuxProperty, source); + source.OnNext(1.2); + source.OnNext("foo"); + + Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); + Assert.True(called); + } + } + + [Fact] + public void Untyped_Style_Binding_Logs_Invalid_Value_Type() + { + var target = new Class1(); + var source = new Subject(); + var called = false; + var expectedMessageTemplate = "Error in binding to {Target}.{Property}: expected {ExpectedType}, got {Value} ({ValueType})"; + + LogCallback checkLogMessage = (level, area, src, mt, pv) => + { + if (level == LogEventLevel.Warning && + area == LogArea.Binding && + mt == expectedMessageTemplate && + src == target && + pv[0].GetType() == typeof(Class1) && + (AvaloniaProperty)pv[1] == Class1.QuxProperty && + (Type)pv[2] == typeof(double) && + (string)pv[3] == "foo" && + (Type)pv[4] == typeof(string)) + { + called = true; + } + }; + + using (TestLogSink.Start(checkLogMessage)) + { + target.Bind(Class1.QuxProperty, source, BindingPriority.Style); + source.OnNext(1.2); + source.OnNext("foo"); + + Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); + Assert.True(called); + } + } + [Fact] public async Task Bind_With_Scheduler_Executes_On_Scheduler() { @@ -726,8 +882,9 @@ namespace Avalonia.Base.UnitTests public void IsAnimating_On_Property_With_Animation_Value_Returns_True() { var target = new Class1(); + var source = new BehaviorSubject>("foo"); - target.SetValue(Class1.FooProperty, "foo", BindingPriority.Animation); + target.Bind(Class1.FooProperty, source, BindingPriority.Animation); Assert.True(target.IsAnimating(Class1.FooProperty)); } @@ -786,7 +943,7 @@ namespace Avalonia.Base.UnitTests target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source }); - Assert.False(source.ValueSetterCalled); + Assert.False(source.SetterCalled); } [Fact] @@ -797,7 +954,7 @@ namespace Avalonia.Base.UnitTests target.Bind(Class1.DoubleValueProperty, new Binding("[0]", BindingMode.TwoWay) { Source = source }); - Assert.False(source.ValueSetterCalled); + Assert.False(source.SetterCalled); } [Fact] @@ -822,7 +979,7 @@ namespace Avalonia.Base.UnitTests public void Disposing_Completed_Binding_Does_Not_Throw() { var target = new Class1(); - var source = new Subject(); + var source = new Subject>(); var subscription = target.Bind(Class1.FooProperty, source); source.OnCompleted(); @@ -830,68 +987,15 @@ namespace Avalonia.Base.UnitTests subscription.Dispose(); } - [Fact] - public void TwoWay_Binding_Should_Not_Call_Setter_On_Creation_With_Value() - { - var target = new Class1(); - var source = new TestTwoWayBindingViewModel() { Value = 1 }; - source.ResetSetterCalled(); - - target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source }); - - Assert.False(source.ValueSetterCalled); - } - - [Fact] - public void TwoWay_Binding_Should_Not_Call_Setter_On_Creation_Indexer_With_Value() - { - var target = new Class1(); - var source = new TestTwoWayBindingViewModel() { [0] = 1 }; - source.ResetSetterCalled(); - - target.Bind(Class1.DoubleValueProperty, new Binding("[0]", BindingMode.TwoWay) { Source = source }); - - Assert.False(source.ValueSetterCalled); - } - - - [Fact] - public void Disposing_a_TwoWay_Binding_Should_Set_Default_Value_On_Binding_Target_But_Not_On_Source() - { - var target = new Class3(); - - // Create a source class which has a Value set to -1 and a Minimum set to -2 - var source = new TestTwoWayBindingViewModel() { Value = -1, Minimum = -2 }; - - // Reset the setter counter - source.ResetSetterCalled(); - - // 1. bind the minimum - var disposable_1 = target.Bind(Class3.MinimumProperty, new Binding("Minimum", BindingMode.TwoWay) { Source = source }); - // 2. Bind the value - var disposable_2 = target.Bind(Class3.ValueProperty, new Binding("Value", BindingMode.TwoWay) { Source = source }); - - // Dispose the minimum binding - disposable_1.Dispose(); - // Dispose the value binding - disposable_2.Dispose(); - - - // The value setter should be called here as we have disposed minimum fist and the default value of minimum is 0, so this should be changed. - Assert.True(source.ValueSetterCalled); - // The minimum value should not be changed in the source. - Assert.False(source.MinimumSetterCalled); - } - /// /// Returns an observable that returns a single value but does not complete. /// /// The type of the observable. /// The value. /// The observable. - private IObservable Single(T value) + private IObservable> Single(T value) { - return Observable.Never().StartWith(value); + return Observable.Never>().StartWith(value); } private class Class1 : AvaloniaObject @@ -918,56 +1022,6 @@ namespace Avalonia.Base.UnitTests AvaloniaProperty.Register("Bar", "bardefault"); } - private class Class3 : AvaloniaObject - { - static Class3() - { - MinimumProperty.Changed.Subscribe(x => OnMinimumChanged(x)); - } - - private static void OnMinimumChanged(AvaloniaPropertyChangedEventArgs e) - { - if (e.Sender is Class3 s) - { - s.SetValue(ValueProperty, MathUtilities.Clamp(s.Value, e.NewValue.Value, double.PositiveInfinity)); - } - } - - /// - /// Defines the property. - /// - public static readonly StyledProperty ValueProperty = - AvaloniaProperty.Register(nameof(Value), 0); - - /// - /// Gets or sets the Value property - /// - public double Value - { - get { return GetValue(ValueProperty); } - set { SetValue(ValueProperty, value); } - } - - - /// - /// Defines the property. - /// - public static readonly StyledProperty MinimumProperty = - AvaloniaProperty.Register(nameof(Minimum), 0); - - /// - /// Gets or sets the minimum property - /// - public double Minimum - { - get { return GetValue(MinimumProperty); } - set { SetValue(MinimumProperty, value); } - } - - - } - - private class TestOneTimeBinding : IBinding { private IObservable _source; @@ -979,8 +1033,8 @@ namespace Avalonia.Base.UnitTests public InstancedBinding Initiate( IAvaloniaObject target, - AvaloniaProperty targetProperty, - object anchor = null, + AvaloniaProperty? targetProperty, + object? anchor = null, bool enableDataValidation = false) { return InstancedBinding.OneTime(_source); @@ -995,7 +1049,7 @@ namespace Avalonia.Base.UnitTests private double _value; - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler? PropertyChanged; public double Value { @@ -1008,8 +1062,10 @@ namespace Avalonia.Base.UnitTests if (SetterInvokedCount < MaxInvokedCount) { _value = (int)value; - if (_value > 75) _value = 75; - if (_value < 25) _value = 25; + if (_value > 75) + _value = 75; + if (_value < 25) + _value = 25; } else { @@ -1032,18 +1088,7 @@ namespace Avalonia.Base.UnitTests set { _value = value; - ValueSetterCalled = true; - } - } - - private double _minimum; - public double Minimum - { - get => _minimum; - set - { - _minimum = value; - MinimumSetterCalled = true; + SetterCalled = true; } } @@ -1053,18 +1098,11 @@ namespace Avalonia.Base.UnitTests set { _value = value; - ValueSetterCalled = true; + SetterCalled = true; } } - public bool ValueSetterCalled { get; private set; } - public bool MinimumSetterCalled { get; private set; } - - public void ResetSetterCalled() - { - ValueSetterCalled = false; - MinimumSetterCalled = false; - } + public bool SetterCalled { get; private set; } } } } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs index 6bd29a1577..c20b75443c 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs @@ -65,53 +65,42 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void GetBaseValue_LocalValue_Ignores_Default_Value() + public void GetBaseValue_Ignores_Default_Value() { var target = new Class3(); target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation); - Assert.False(target.GetBaseValue(Class1.FooProperty, BindingPriority.LocalValue).HasValue); + Assert.False(target.GetBaseValue(Class1.FooProperty).HasValue); } [Fact] - public void GetBaseValue_LocalValue_Returns_Local_Value() + public void GetBaseValue_Returns_Local_Value() { var target = new Class3(); target.SetValue(Class1.FooProperty, "local"); target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation); - Assert.Equal("local", target.GetBaseValue(Class1.FooProperty, BindingPriority.LocalValue).Value); + Assert.Equal("local", target.GetBaseValue(Class1.FooProperty).Value); } [Fact] - public void GetBaseValue_LocalValue_Returns_Style_Value() + public void GetBaseValue_Returns_Style_Value() { var target = new Class3(); target.SetValue(Class1.FooProperty, "style", BindingPriority.Style); target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation); - Assert.Equal("style", target.GetBaseValue(Class1.FooProperty, BindingPriority.LocalValue).Value); + Assert.Equal("style", target.GetBaseValue(Class1.FooProperty).Value); } [Fact] - public void GetBaseValue_Style_Ignores_LocalValue_Animated_Value() + public void GetBaseValue_Returns_Style_Value_Set_Via_Untyped_Setters() { var target = new Class3(); - target.Bind(Class1.FooProperty, new BehaviorSubject("animated"), BindingPriority.Animation); - target.SetValue(Class1.FooProperty, "local"); - Assert.False(target.GetBaseValue(Class1.FooProperty, BindingPriority.Style).HasValue); - } - - [Fact] - public void GetBaseValue_Style_Returns_Style_Value() - { - var target = new Class3(); - - target.SetValue(Class1.FooProperty, "local"); - target.SetValue(Class1.FooProperty, "style", BindingPriority.Style); - target.Bind(Class1.FooProperty, new BehaviorSubject("animated"), BindingPriority.Animation); - Assert.Equal("style", target.GetBaseValue(Class1.FooProperty, BindingPriority.Style)); + target.SetValue(Class1.FooProperty, (object)"style", BindingPriority.Style); + target.SetValue(Class1.FooProperty, (object)"animated", BindingPriority.Animation); + Assert.Equal("style", target.GetBaseValue(Class1.FooProperty).Value); } private class Class1 : AvaloniaObject diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Inheritance.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Inheritance.cs index 9e9ae4ec74..556440178d 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Inheritance.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Inheritance.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Avalonia.Data; using Xunit; namespace Avalonia.Base.UnitTests @@ -6,7 +7,17 @@ namespace Avalonia.Base.UnitTests public class AvaloniaObjectTests_Inheritance { [Fact] - public void GetValue_Returns_Inherited_Value() + public void GetValue_Returns_Inherited_Value_1() + { + Class1 parent = new Class1(); + parent.SetValue(Class1.BazProperty, "changed"); + + Class2 child = new Class2 { Parent = parent }; + Assert.Equal("changed", child.GetValue(Class1.BazProperty)); + } + + [Fact] + public void GetValue_Returns_Inherited_Value_2() { Class1 parent = new Class1(); Class2 child = new Class2 { Parent = parent }; @@ -17,7 +28,23 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void Setting_InheritanceParent_Raises_PropertyChanged_When_Value_Changed_In_Parent() + public void ClearValue_Clears_Inherited_Value() + { + Class1 parent = new Class1(); + Class2 child = new Class2 { Parent = parent }; + + parent.SetValue(Class1.BazProperty, "changed"); + + Assert.Equal("changed", child.GetValue(Class1.BazProperty)); + + parent.ClearValue(Class1.BazProperty); + + Assert.Equal("bazdefault", parent.GetValue(Class1.BazProperty)); + Assert.Equal("bazdefault", child.GetValue(Class1.BazProperty)); + } + + [Fact] + public void Setting_InheritanceParent_Raises_PropertyChanged_When_Parent_Has_Value_Set() { bool raised = false; @@ -29,15 +56,17 @@ namespace Avalonia.Base.UnitTests raised = s == child && e.Property == Class1.BazProperty && (string)e.OldValue == "bazdefault" && - (string)e.NewValue == "changed"; + (string)e.NewValue == "changed" && + e.Priority == BindingPriority.Inherited; child.Parent = parent; Assert.True(raised); + Assert.Equal("changed", child.GetValue(Class1.BazProperty)); } [Fact] - public void Setting_InheritanceParent_Raises_PropertyChanged_For_Attached_Property_When_Value_Changed_In_Parent() + public void Setting_InheritanceParent_Raises_PropertyChanged_For_Attached_Property_When_Parent_Has_Value_Set() { bool raised = false; @@ -54,6 +83,7 @@ namespace Avalonia.Base.UnitTests child.Parent = parent; Assert.True(raised); + Assert.Equal("changed", child.GetValue(AttachedOwner.AttachedProperty)); } [Fact] @@ -71,6 +101,7 @@ namespace Avalonia.Base.UnitTests child.Parent = parent; Assert.False(raised); + Assert.Equal("localvalue", child.GetValue(Class1.BazProperty)); } [Fact] @@ -91,6 +122,7 @@ namespace Avalonia.Base.UnitTests parent.SetValue(Class1.BazProperty, "changed"); Assert.True(raised); + Assert.Equal("changed", child.GetValue(Class1.BazProperty)); } [Fact] @@ -111,6 +143,7 @@ namespace Avalonia.Base.UnitTests parent.SetValue(AttachedOwner.AttachedProperty, "changed"); Assert.True(raised); + Assert.Equal("changed", child.GetValue(AttachedOwner.AttachedProperty)); } [Fact] @@ -128,6 +161,85 @@ namespace Avalonia.Base.UnitTests Assert.Equal(new[] { parent, child }, result); } + [Fact] + public void Reparenting_Raises_PropertyChanged_For_Old_And_New_Inherited_Values() + { + var oldParent = new Class1(); + oldParent.SetValue(Class1.BazProperty, "oldvalue"); + + var newParent = new Class1(); + newParent.SetValue(Class1.BazProperty, "newvalue"); + + var child = new Class2 { Parent = oldParent }; + var raised = 0; + + child.PropertyChanged += (s, e) => + { + Assert.Equal(child, e.Sender); + Assert.Equal("oldvalue", e.GetOldValue()); + Assert.Equal("newvalue", e.GetNewValue()); + Assert.Equal(BindingPriority.Inherited, e.Priority); + ++raised; + }; + + child.Parent = newParent; + + Assert.Equal(1, raised); + Assert.Equal("newvalue", child.GetValue(Class1.BazProperty)); + } + + [Fact] + public void Reparenting_Raises_PropertyChanged_On_GrandChild_For_Old_And_New_Inherited_Values() + { + var oldParent = new Class1(); + oldParent.SetValue(Class1.BazProperty, "oldvalue"); + + var newParent = new Class1(); + newParent.SetValue(Class1.BazProperty, "newvalue"); + + var child = new Class2 { Parent = oldParent }; + var grandchild = new Class2 { Parent = child }; + var raised = 0; + + grandchild.PropertyChanged += (s, e) => + { + Assert.Equal(grandchild, e.Sender); + Assert.Equal("oldvalue", e.GetOldValue()); + Assert.Equal("newvalue", e.GetNewValue()); + Assert.Equal(BindingPriority.Inherited, e.Priority); + ++raised; + }; + + child.Parent = newParent; + + Assert.Equal(1, raised); + Assert.Equal("newvalue", grandchild.GetValue(Class1.BazProperty)); + } + + [Fact] + public void Reparenting_Retains_Inherited_Property_Set_On_Child() + { + var oldParent = new Class1(); + oldParent.SetValue(Class1.BazProperty, "oldvalue"); + + var newParent = new Class1(); + newParent.SetValue(Class1.BazProperty, "newvalue"); + + var child = new Class2 { Parent = oldParent }; + child.SetValue(Class1.BazProperty, "childvalue"); + + var grandchild = new Class2 { Parent = child }; + var raised = 0; + + grandchild.PropertyChanged += (s, e) => ++raised; + + child.Parent = newParent; + + Assert.Equal(0, raised); + Assert.Equal("childvalue", child.GetValue(Class1.BazProperty)); + Assert.Equal("childvalue", grandchild.GetValue(Class1.BazProperty)); + } + private class Class1 : AvaloniaObject { public static readonly StyledProperty FooProperty = diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs index 7f4dcace71..6f34865aa1 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs @@ -35,7 +35,7 @@ namespace Avalonia.Base.UnitTests { var target = new Class1(); - target.SetValue(Class1.FooProperty, "newvalue"); + target.SetValue(Class1.FooProperty, "newvalue", BindingPriority.Animation); target.SetValue(Class1.FooProperty, "styled", BindingPriority.Style); Assert.Equal(2, target.CoreChanges.Count); @@ -48,47 +48,12 @@ namespace Avalonia.Base.UnitTests Assert.False(change.IsEffectiveValueChange); } - [Fact] - public void OnPropertyChangedCore_Is_Called_On_All_Binding_Property_Changes() - { - var target = new Class1(); - var style = new Subject>(); - var animation = new Subject>(); - var templatedParent = new Subject>(); - - target.Bind(Class1.FooProperty, style, BindingPriority.Style); - target.Bind(Class1.FooProperty, animation, BindingPriority.Animation); - target.Bind(Class1.FooProperty, templatedParent, BindingPriority.TemplatedParent); - - style.OnNext("style1"); - templatedParent.OnNext("tp1"); - animation.OnNext("a1"); - templatedParent.OnNext("tp2"); - templatedParent.OnCompleted(); - animation.OnNext("a2"); - style.OnNext("style2"); - style.OnCompleted(); - animation.OnCompleted(); - - var changes = target.CoreChanges.Cast>(); - - Assert.Equal( - new[] { true, true, true, false, false, true, false, false, true }, - changes.Select(x => x.IsEffectiveValueChange).ToList()); - Assert.Equal( - new[] { "style1", "tp1", "a1", "tp2", "$unset", "a2", "style2", "$unset", "foodefault" }, - changes.Select(x => x.NewValue.GetValueOrDefault("$unset")).ToList()); - Assert.Equal( - new[] { "foodefault", "style1", "tp1", "$unset", "$unset", "a1", "$unset", "$unset", "a2" }, - changes.Select(x => x.OldValue.GetValueOrDefault("$unset")).ToList()); - } - [Fact] public void OnPropertyChanged_Is_Called_Only_For_Effective_Value_Changes() { var target = new Class1(); - target.SetValue(Class1.FooProperty, "newvalue"); + target.SetValue(Class1.FooProperty, "newvalue", BindingPriority.Animation); target.SetValue(Class1.FooProperty, "styled", BindingPriority.Style); Assert.Equal(1, target.Changes.Count); @@ -124,19 +89,13 @@ namespace Avalonia.Base.UnitTests private static AvaloniaPropertyChangedEventArgs Clone(AvaloniaPropertyChangedEventArgs change) { var e = (AvaloniaPropertyChangedEventArgs)change; - var result = new AvaloniaPropertyChangedEventArgs( + return new AvaloniaPropertyChangedEventArgs( change.Sender, e.Property, e.OldValue, e.NewValue, - change.Priority); - - if (!change.IsEffectiveValueChange) - { - result.MarkNonEffectiveValue(); - } - - return result; + change.Priority, + change.IsEffectiveValueChange); } } } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs index c14332e1fe..e8175cf477 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs @@ -1,6 +1,7 @@ using System; using System.Reactive.Subjects; using Avalonia.Controls; +using Avalonia.Data; using Xunit; namespace Avalonia.Base.UnitTests @@ -41,7 +42,7 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void Reverts_To_DefaultValue_If_Binding_Fails_Validation() + public void Reverts_To_DefaultValue_If_LocalValue_Binding_Fails_Validation() { var target = new Class1(); var source = new Subject(); @@ -52,6 +53,31 @@ namespace Avalonia.Base.UnitTests Assert.Equal(11, target.GetValue(Class1.FooProperty)); } + [Fact] + public void Reverts_To_DefaultValue_If_Style_Binding_Fails_Validation() + { + var target = new Class1(); + var source = new Subject(); + + target.Bind(Class1.FooProperty, source, BindingPriority.Style); + source.OnNext(150); + + Assert.Equal(11, target.GetValue(Class1.FooProperty)); + } + + [Fact] + public void Reverts_To_Lower_Priority_If_Style_Binding_Fails_Validation() + { + var target = new Class1(); + var source = new Subject(); + + target.SetValue(Class1.FooProperty, 10, BindingPriority.Style); + target.Bind(Class1.FooProperty, source, BindingPriority.StyleTrigger); + source.OnNext(150); + + Assert.Equal(10, target.GetValue(Class1.FooProperty)); + } + [Fact] public void Reverts_To_DefaultValue_Even_In_Presence_Of_Other_Bindings() { diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs index f3f39b465b..a51899617b 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Avalonia.Data; +using Avalonia.PropertyStore; using Avalonia.Styling; using Avalonia.Utilities; using Xunit; @@ -149,28 +150,31 @@ namespace Avalonia.Base.UnitTests internal override IDisposable RouteBind( AvaloniaObject o, - IObservable> source, + IObservable source, BindingPriority priority) { throw new NotImplementedException(); } - internal override void RouteClearValue(AvaloniaObject o) + internal override IDisposable RouteBind( + AvaloniaObject o, + IObservable> source, + BindingPriority priority) { throw new NotImplementedException(); } - internal override object RouteGetValue(AvaloniaObject o) + internal override void RouteClearValue(AvaloniaObject o) { throw new NotImplementedException(); } - internal override object RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority) + internal override object RouteGetValue(AvaloniaObject o) { throw new NotImplementedException(); } - internal override void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject oldParent) + internal override object RouteGetBaseValue(AvaloniaObject o) { throw new NotImplementedException(); } @@ -183,7 +187,7 @@ namespace Avalonia.Base.UnitTests throw new NotImplementedException(); } - internal override ISetterInstance CreateSetterInstance(IStyleable target, object value) + internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs deleted file mode 100644 index aa5993f3b2..0000000000 --- a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs +++ /dev/null @@ -1,314 +0,0 @@ -using System; -using System.Linq; -using System.Reactive.Disposables; -using Avalonia.Data; -using Avalonia.PropertyStore; -using Moq; -using Xunit; - -namespace Avalonia.Base.UnitTests -{ - public class PriorityValueTests - { - private static readonly AvaloniaObject Owner = new AvaloniaObject(); - private static readonly ValueStore ValueStore = new ValueStore(Owner); - private static readonly StyledProperty TestProperty = new StyledProperty( - "Test", - typeof(PriorityValueTests), - new StyledPropertyMetadata()); - - [Fact] - public void Constructor_Should_Set_Value_Based_On_Initial_Entry() - { - var target = new PriorityValue( - Owner, - TestProperty, - ValueStore, - new ConstantValueEntry( - TestProperty, - "1", - BindingPriority.StyleTrigger, - new(ValueStore))); - - Assert.Equal("1", target.GetValue().Value); - Assert.Equal(BindingPriority.StyleTrigger, target.Priority); - } - - [Fact] - public void GetValue_Should_Respect_MaxPriority() - { - var target = new PriorityValue( - Owner, - TestProperty, - ValueStore); - - target.SetValue("animation", BindingPriority.Animation); - target.SetValue("local", BindingPriority.LocalValue); - target.SetValue("styletrigger", BindingPriority.StyleTrigger); - target.SetValue("style", BindingPriority.Style); - - Assert.Equal("animation", target.GetValue(BindingPriority.Animation)); - Assert.Equal("local", target.GetValue(BindingPriority.LocalValue)); - Assert.Equal("styletrigger", target.GetValue(BindingPriority.StyleTrigger)); - Assert.Equal("style", target.GetValue(BindingPriority.TemplatedParent)); - Assert.Equal("style", target.GetValue(BindingPriority.Style)); - } - - [Fact] - public void SetValue_LocalValue_Should_Not_Add_Entries() - { - var target = new PriorityValue( - Owner, - TestProperty, - ValueStore); - - target.SetValue("1", BindingPriority.LocalValue); - target.SetValue("2", BindingPriority.LocalValue); - - Assert.Empty(target.Entries); - } - - [Fact] - public void SetValue_Non_LocalValue_Should_Add_Entries() - { - var target = new PriorityValue( - Owner, - TestProperty, - ValueStore); - - target.SetValue("1", BindingPriority.Style); - target.SetValue("2", BindingPriority.Animation); - - var result = target.Entries - .OfType>() - .Select(x => x.GetValue().Value) - .ToList(); - - Assert.Equal(new[] { "1", "2" }, result); - } - - [Fact] - public void Priority_Should_Be_Set() - { - var target = new PriorityValue( - Owner, - TestProperty, - ValueStore); - - Assert.Equal(BindingPriority.Unset, target.Priority); - target.SetValue("style", BindingPriority.Style); - Assert.Equal(BindingPriority.Style, target.Priority); - target.SetValue("local", BindingPriority.LocalValue); - Assert.Equal(BindingPriority.LocalValue, target.Priority); - target.SetValue("animation", BindingPriority.Animation); - Assert.Equal(BindingPriority.Animation, target.Priority); - target.SetValue("local2", BindingPriority.LocalValue); - Assert.Equal(BindingPriority.Animation, target.Priority); - } - - [Fact] - public void Binding_With_Same_Priority_Should_Be_Appended() - { - var target = new PriorityValue(Owner, TestProperty, ValueStore); - var source1 = new Source("1"); - var source2 = new Source("2"); - - target.AddBinding(source1, BindingPriority.LocalValue); - target.AddBinding(source2, BindingPriority.LocalValue); - - var result = target.Entries - .OfType>() - .Select(x => x.Source) - .OfType() - .Select(x => x.Id) - .ToList(); - - Assert.Equal(new[] { "1", "2" }, result); - } - - [Fact] - public void Binding_With_Higher_Priority_Should_Be_Appended() - { - var target = new PriorityValue(Owner, TestProperty, ValueStore); - var source1 = new Source("1"); - var source2 = new Source("2"); - - target.AddBinding(source1, BindingPriority.LocalValue); - target.AddBinding(source2, BindingPriority.Animation); - - var result = target.Entries - .OfType>() - .Select(x => x.Source) - .OfType() - .Select(x => x.Id) - .ToList(); - - Assert.Equal(new[] { "1", "2" }, result); - } - - [Fact] - public void Binding_With_Lower_Priority_Should_Be_Prepended() - { - var target = new PriorityValue(Owner, TestProperty, ValueStore); - var source1 = new Source("1"); - var source2 = new Source("2"); - - target.AddBinding(source1, BindingPriority.LocalValue); - target.AddBinding(source2, BindingPriority.Style); - - var result = target.Entries - .OfType>() - .Select(x => x.Source) - .OfType() - .Select(x => x.Id) - .ToList(); - - Assert.Equal(new[] { "2", "1" }, result); - } - - [Fact] - public void Second_Binding_With_Lower_Priority_Should_Be_Inserted_In_Middle() - { - var target = new PriorityValue(Owner, TestProperty, ValueStore); - var source1 = new Source("1"); - var source2 = new Source("2"); - var source3 = new Source("3"); - - target.AddBinding(source1, BindingPriority.LocalValue); - target.AddBinding(source2, BindingPriority.Style); - target.AddBinding(source3, BindingPriority.Style); - - var result = target.Entries - .OfType>() - .Select(x => x.Source) - .OfType() - .Select(x => x.Id) - .ToList(); - - Assert.Equal(new[] { "2", "3", "1" }, result); - } - - [Fact] - public void Competed_Binding_Should_Be_Removed() - { - var target = new PriorityValue(Owner, TestProperty, ValueStore); - var source1 = new Source("1"); - var source2 = new Source("2"); - var source3 = new Source("3"); - - target.AddBinding(source1, BindingPriority.LocalValue).Start(); - target.AddBinding(source2, BindingPriority.Style).Start(); - target.AddBinding(source3, BindingPriority.Style).Start(); - source3.OnCompleted(); - - var result = target.Entries - .OfType>() - .Select(x => x.Source) - .OfType() - .Select(x => x.Id) - .ToList(); - - Assert.Equal(new[] { "2", "1" }, result); - } - - [Fact] - public void Value_Should_Come_From_Last_Entry() - { - var target = new PriorityValue(Owner, TestProperty, ValueStore); - var source1 = new Source("1"); - var source2 = new Source("2"); - var source3 = new Source("3"); - - target.AddBinding(source1, BindingPriority.LocalValue).Start(); - target.AddBinding(source2, BindingPriority.Style).Start(); - target.AddBinding(source3, BindingPriority.Style).Start(); - - Assert.Equal("1", target.GetValue().Value); - } - - [Fact] - public void LocalValue_Should_Override_LocalValue_Binding() - { - var target = new PriorityValue(Owner, TestProperty, ValueStore); - var source1 = new Source("1"); - - target.AddBinding(source1, BindingPriority.LocalValue).Start(); - target.SetValue("2", BindingPriority.LocalValue); - - Assert.Equal("2", target.GetValue().Value); - } - - [Fact] - public void LocalValue_Should_Override_Style_Binding() - { - var target = new PriorityValue(Owner, TestProperty, ValueStore); - var source1 = new Source("1"); - - target.AddBinding(source1, BindingPriority.Style).Start(); - target.SetValue("2", BindingPriority.LocalValue); - - Assert.Equal("2", target.GetValue().Value); - } - - [Fact] - public void LocalValue_Should_Not_Override_Animation_Binding() - { - var target = new PriorityValue(Owner, TestProperty, ValueStore); - var source1 = new Source("1"); - - target.AddBinding(source1, BindingPriority.Animation).Start(); - target.SetValue("2", BindingPriority.LocalValue); - - Assert.Equal("1", target.GetValue().Value); - } - - [Fact] - public void NonAnimated_Value_Should_Be_Correct_1() - { - var target = new PriorityValue(Owner, TestProperty, ValueStore); - var source1 = new Source("1"); - var source2 = new Source("2"); - var source3 = new Source("3"); - - target.AddBinding(source1, BindingPriority.LocalValue).Start(); - target.AddBinding(source2, BindingPriority.Style).Start(); - target.AddBinding(source3, BindingPriority.Animation).Start(); - - Assert.Equal("3", target.GetValue().Value); - Assert.Equal("1", target.GetValue(BindingPriority.LocalValue).Value); - } - - [Fact] - public void NonAnimated_Value_Should_Be_Correct_2() - { - var target = new PriorityValue(Owner, TestProperty, ValueStore); - var source1 = new Source("1"); - var source2 = new Source("2"); - var source3 = new Source("3"); - - target.AddBinding(source1, BindingPriority.Animation).Start(); - target.AddBinding(source2, BindingPriority.Style).Start(); - target.AddBinding(source3, BindingPriority.Style).Start(); - - Assert.Equal("1", target.GetValue().Value); - Assert.Equal("3", target.GetValue(BindingPriority.LocalValue).Value); - } - - private class Source : IObservable> - { - private IObserver> _observer; - - public Source(string id) => Id = id; - public string Id { get; } - - public IDisposable Subscribe(IObserver> observer) - { - _observer = observer; - observer.OnNext(Id); - return Disposable.Empty; - } - - public void OnCompleted() => _observer.OnCompleted(); - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs b/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs new file mode 100644 index 0000000000..0c87083f8d --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs @@ -0,0 +1,126 @@ +using System.Collections.Generic; +using System.Reactive; +using System.Reactive.Subjects; +using Avalonia.PropertyStore; +using Avalonia.Styling; +using Microsoft.Reactive.Testing; +using Xunit; +using static Microsoft.Reactive.Testing.ReactiveTest; + +#nullable enable + +namespace Avalonia.Base.UnitTests.PropertyStore +{ + public class ValueStoreTests_Frames + { + [Fact] + public void Adding_Frame_Raises_PropertyChanged() + { + var target = new Class1(); + var subject = new BehaviorSubject("bar"); + var result = new List(); + var style = new Style + { + Setters = + { + new Setter(Class1.FooProperty, "foo"), + new Setter(Class1.BarProperty, subject.ToBinding()), + } + }; + + target.PropertyChanged += (s, e) => + { + result.Add(new(e.Property, e.OldValue, e.NewValue)); + }; + + var frame = InstanceStyle(style, target); + target.GetValueStore().AddFrame(frame); + + Assert.Equal(new PropertyChange[] + { + new(Class1.FooProperty, "foodefault", "foo"), + new(Class1.BarProperty, "bardefault", "bar"), + }, result); + } + + [Fact] + public void Removing_Frame_Raises_PropertyChanged() + { + var target = new Class1(); + var subject = new BehaviorSubject("bar"); + var result = new List(); + var style = new Style + { + Setters = + { + new Setter(Class1.FooProperty, "foo"), + new Setter(Class1.BarProperty, subject.ToBinding()), + } + }; + var frame = InstanceStyle(style, target); + target.GetValueStore().AddFrame(frame); + + target.PropertyChanged += (s, e) => + { + result.Add(new(e.Property, e.OldValue, e.NewValue)); + }; + + target.GetValueStore().RemoveFrame(frame); + + Assert.Equal(new PropertyChange[] + { + new(Class1.FooProperty, "foo", "foodefault"), + new(Class1.BarProperty, "bar", "bardefault"), + }, result); + } + + [Fact] + public void Removing_Frame_Unsubscribes_Binding() + { + var target = new Class1(); + var scheduler = new TestScheduler(); + var obs = scheduler.CreateColdObservable(OnNext(0, "bar")); + var result = new List(); + var style = new Style + { + Setters = + { + new Setter(Class1.FooProperty, "foo"), + new Setter(Class1.BarProperty, obs.ToBinding()), + } + }; + var frame = InstanceStyle(style, target); + + target.GetValueStore().AddFrame(frame); + target.GetValueStore().RemoveFrame(frame); + + Assert.Single(obs.Subscriptions); + Assert.Equal(0, obs.Subscriptions[0].Subscribe); + Assert.NotEqual(Subscription.Infinite, obs.Subscriptions[0].Unsubscribe); + } + + private static StyleInstance InstanceStyle(Style style, StyledElement target) + { + var result = new StyleInstance(style, null); + + foreach (var setter in style.Setters) + result.Add(setter.Instance(result, target)); + + return result; + } + + private class Class1 : StyledElement + { + public static readonly StyledProperty FooProperty = + AvaloniaProperty.Register("Foo", "foodefault"); + + public static readonly StyledProperty BarProperty = + AvaloniaProperty.Register("Bar", "bardefault", true); + } + + private record PropertyChange( + AvaloniaProperty Property, + object? OldValue, + object? NewValue); + } +} diff --git a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs index b57a024f41..c3152ed01a 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs @@ -5,11 +5,14 @@ using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Data.Converters; -using Avalonia.Diagnostics; +using Avalonia.Media; using Avalonia.Styling; +using Avalonia.UnitTests; using Moq; using Xunit; +#nullable enable + namespace Avalonia.Base.UnitTests.Styling { public class SetterTests @@ -28,13 +31,13 @@ namespace Avalonia.Base.UnitTests.Styling var control = new TextBlock(); var subject = new BehaviorSubject("foo"); var descriptor = InstancedBinding.OneWay(subject); - var binding = Mock.Of(x => x.Initiate(control, TextBlock.TextProperty, null, false) == descriptor); + var binding = Mock.Of(x => x.Initiate(control, TextBlock.TagProperty, null, false) == descriptor); var style = Mock.Of(); - var setter = new Setter(TextBlock.TextProperty, binding); + var setter = new Setter(TextBlock.TagProperty, binding); - setter.Instance(control).Start(false); + Apply(setter, control); - Assert.Equal("foo", control.Text); + Assert.Equal("foo", control.Tag); } [Fact] @@ -47,7 +50,7 @@ namespace Avalonia.Base.UnitTests.Styling var style = Mock.Of(); var setter = new Setter(TextBlock.TagProperty, binding); - setter.Instance(control).Start(false); + Apply(setter, control); Assert.Equal(null, control.Text); } @@ -60,133 +63,309 @@ namespace Avalonia.Base.UnitTests.Styling var style = Mock.Of(); var setter = new Setter(Decorator.ChildProperty, template); - setter.Instance(control).Start(false); + Apply(setter, control); Assert.IsType(control.Child); } + [Fact] + public void Can_Set_Direct_Property_In_Style_Without_Activator() + { + var control = new TextBlock(); + var target = new Setter(); + var style = new Style(x => x.Is()) + { + Setters = + { + new Setter(TextBlock.TextProperty, "foo"), + } + }; + + Apply(style, control); + + Assert.Equal("foo", control.Text); + } + + [Fact] + public void Can_Set_Direct_Property_Binding_In_Style_Without_Activator() + { + var control = new TextBlock(); + var target = new Setter(); + var source = new BehaviorSubject("foo"); + var style = new Style(x => x.Is()) + { + Setters = + { + new Setter(TextBlock.TextProperty, source.ToBinding()), + } + }; + + Apply(style, control); + + Assert.Equal("foo", control.Text); + } + + [Fact] + public void Cannot_Set_Direct_Property_Binding_In_Style_With_Activator() + { + var control = new TextBlock(); + var target = new Setter(); + var source = new BehaviorSubject("foo"); + var style = new Style(x => x.Is().Class("foo")) + { + Setters = + { + new Setter(TextBlock.TextProperty, source.ToBinding()), + } + }; + + Assert.Throws(() => Apply(style, control)); + } + + [Fact] + public void Cannot_Set_Direct_Property_In_Style_With_Activator() + { + var control = new TextBlock(); + var target = new Setter(); + var style = new Style(x => x.Is().Class("foo")) + { + Setters = + { + new Setter(TextBlock.TextProperty, "foo"), + } + }; + + Assert.Throws(() => Apply(style, control)); + } + [Fact] public void Does_Not_Call_Converter_ConvertBack_On_OneWay_Binding() { - var control = new Decorator { Name = "foo" }; - var style = Mock.Of(); + var control = new Decorator + { + Name = "foo", + Classes = { "foo" }, + }; + var binding = new Binding("Name", BindingMode.OneWay) { Converter = new TestConverter(), RelativeSource = new RelativeSource(RelativeSourceMode.Self), }; - var setter = new Setter(Decorator.TagProperty, binding); - var instance = setter.Instance(control); - instance.Start(true); - instance.Activate(); + var style = new Style(x => x.OfType().Class("foo")) + { + Setters = + { + new Setter(Decorator.TagProperty, binding) + }, + }; + + Apply(style, control); Assert.Equal("foobar", control.Tag); // Issue #1218 caused TestConverter.ConvertBack to throw here. - instance.Deactivate(); + control.Classes.Remove("foo"); Assert.Null(control.Tag); } [Fact] public void Setter_Should_Apply_Value_Without_Activator_With_Style_Priority() { - var control = new Control(); - var setter = new Setter(TextBlock.TagProperty, "foo"); + var control = new Border(); + var style = new Style(x => x.OfType()) + { + Setters = + { + new Setter(Control.TagProperty, "foo"), + }, + }; + var raised = 0; - setter.Instance(control).Start(false); + control.PropertyChanged += (s, e) => + { + Assert.Equal(Control.TagProperty, e.Property); + Assert.Equal(BindingPriority.Style, e.Priority); + ++raised; + }; - Assert.Equal("foo", control.Tag); - Assert.Equal(BindingPriority.Style, control.GetDiagnostic(TextBlock.TagProperty).Priority); + Apply(style, control); + + Assert.Equal(1, raised); } [Fact] - public void Setter_Should_Apply_Value_With_Activator_As_Binding_With_StyleTrigger_Priority() + public void Setter_Should_Apply_Value_With_Activator_With_StyleTrigger_Priority() { - var control = new Canvas(); - var setter = new Setter(TextBlock.TagProperty, "foo"); + var control = new Border { Classes = { "foo" } }; + var style = new Style(x => x.OfType().Class("foo")) + { + Setters = + { + new Setter(Control.TagProperty, "foo"), + }, + }; + var activator = new Subject(); + var raised = 0; + + control.PropertyChanged += (s, e) => + { + Assert.Equal(Border.TagProperty, e.Property); + Assert.Equal(BindingPriority.StyleTrigger, e.Priority); + ++raised; + }; - var instance = setter.Instance(control); - instance.Start(true); - instance.Activate(); + Apply(style, control); - Assert.Equal("foo", control.Tag); - Assert.Equal(BindingPriority.StyleTrigger, control.GetDiagnostic(TextBlock.TagProperty).Priority); + Assert.Equal(1, raised); } [Fact] public void Setter_Should_Apply_Binding_Without_Activator_With_Style_Priority() { - var control = new Canvas(); - var source = new { Foo = "foo" }; - var setter = new Setter(TextBlock.TagProperty, new Binding + var control = new Border { - Source = source, - Path = nameof(source.Foo), - }); + DataContext = "foo", + }; - setter.Instance(control).Start(false); + var style = new Style(x => x.OfType()) + { + Setters = + { + new Setter(Control.TagProperty, new Binding()), + }, + }; - Assert.Equal("foo", control.Tag); - Assert.Equal(BindingPriority.Style, control.GetDiagnostic(TextBlock.TagProperty).Priority); + var raised = 0; + + control.PropertyChanged += (s, e) => + { + Assert.Equal(Control.TagProperty, e.Property); + Assert.Equal(BindingPriority.Style, e.Priority); + ++raised; + }; + + Apply(style, control); + + Assert.Equal(1, raised); } [Fact] public void Setter_Should_Apply_Binding_With_Activator_With_StyleTrigger_Priority() { - var control = new Canvas(); - var source = new { Foo = "foo" }; - var setter = new Setter(TextBlock.TagProperty, new Binding + var control = new Border { - Source = source, - Path = nameof(source.Foo), - }); + Classes = { "foo" }, + DataContext = "foo", + }; - var instance = setter.Instance(control); - instance.Start(true); - instance.Activate(); + var style = new Style(x => x.OfType().Class("foo")) + { + Setters = + { + new Setter(Control.TagProperty, new Binding()), + }, + }; - Assert.Equal("foo", control.Tag); - Assert.Equal(BindingPriority.StyleTrigger, control.GetDiagnostic(TextBlock.TagProperty).Priority); + var raised = 0; + + control.PropertyChanged += (s, e) => + { + Assert.Equal(Control.TagProperty, e.Property); + Assert.Equal(BindingPriority.StyleTrigger, e.Priority); + ++raised; + }; + + Apply(style, control); + + Assert.Equal(1, raised); } [Fact] - public void Disposing_Setter_Should_Preserve_LocalValue() + public void Direct_Property_Setter_With_TwoWay_Binding_Should_Update_Source() { - var control = new Canvas(); - var setter = new Setter(TextBlock.TagProperty, "foo"); - - var instance = setter.Instance(control); - instance.Start(true); - instance.Activate(); + using var app = UnitTestApplication.Start(TestServices.MockThreadingInterface); + var data = new Data { Foo = "foo" }; + var control = new TextBox + { + DataContext = data, + }; - control.Tag = "bar"; + var style = new Style(x => x.OfType()) + { + Setters = + { + new Setter + { + Property = TextBox.TextProperty, + Value = new Binding + { + Path = "Foo", + Mode = BindingMode.TwoWay + } + } + }, + }; - instance.Dispose(); + Apply(style, control); + Assert.Equal("foo", control.Text); - Assert.Equal("bar", control.Tag); + control.Text = "bar"; + Assert.Equal("bar", data.Foo); } [Fact] - public void Disposing_Binding_Setter_Should_Preserve_LocalValue() + public void Styled_Property_Setter_With_TwoWay_Binding_Should_Update_Source() { - var control = new Canvas(); - var source = new { Foo = "foo" }; - var setter = new Setter(TextBlock.TagProperty, new Binding + var data = new Data { Bar = Brushes.Red }; + var control = new Border { - Source = source, - Path = nameof(source.Foo), - }); + DataContext = data, + }; - var instance = setter.Instance(control); - instance.Start(true); - instance.Activate(); + var style = new Style(x => x.OfType()) + { + Setters = + { + new Setter + { + Property = Border.BackgroundProperty, + Value = new Binding + { + Path = "Bar", + Mode = BindingMode.TwoWay + } + } + }, + }; + + Apply(style, control); + Assert.Equal(Brushes.Red, control.Background); + + control.Background = Brushes.Green; + Assert.Equal(Brushes.Green, data.Bar); + } + + private void Apply(Style style, Control control) + { + style.TryAttach(control, null); + } - control.Tag = "bar"; + private void Apply(Setter setter, Control control) + { + var style = new Style(x => x.Is()) + { + Setters = { setter }, + }; - instance.Dispose(); + Apply(style, control); + } - Assert.Equal("bar", control.Tag); + private class Data + { + public string? Foo { get; set; } + public IBrush? Bar { get; set; } } private class TestConverter : IValueConverter diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs b/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs index f2dfe66054..d1d4120c9b 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Diagnostics; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -146,7 +147,7 @@ namespace Avalonia.Base.UnitTests.Styling target.Classes.Add("foo"); target.Classes.Remove("foo"); - Assert.Equal(new[] { "foodefault", "Foo", "Bar", "foodefault" }, values); + Assert.Equal(new[] { "foodefault", "Bar", "foodefault" }, values); } [Fact] @@ -463,39 +464,40 @@ namespace Avalonia.Base.UnitTests.Styling [Fact] public void Template_In_Inactive_Style_Is_Not_Built() { - var instantiationCount = 0; - var template = new FuncTemplate(() => - { - ++instantiationCount; - return new Class1(); - }); - - Styles styles = new Styles - { - new Style(x => x.OfType()) - { - Setters = - { - new Setter(Class1.ChildProperty, template), - }, - }, - - new Style(x => x.OfType()) - { - Setters = - { - new Setter(Class1.ChildProperty, template), - }, - } - }; - - var target = new Class1(); - target.BeginBatchUpdate(); - styles.TryAttach(target, null); - target.EndBatchUpdate(); - - Assert.NotNull(target.Child); - Assert.Equal(1, instantiationCount); + throw new NotImplementedException(); + ////var instantiationCount = 0; + ////var template = new FuncTemplate(() => + ////{ + //// ++instantiationCount; + //// return new Class1(); + ////}); + + ////Styles styles = new Styles + ////{ + //// new Style(x => x.OfType()) + //// { + //// Setters = + //// { + //// new Setter(Class1.ChildProperty, template), + //// }, + //// }, + + //// new Style(x => x.OfType()) + //// { + //// Setters = + //// { + //// new Setter(Class1.ChildProperty, template), + //// }, + //// } + ////}; + + ////var target = new Class1(); + ////target.BeginBatchUpdate(); + ////styles.TryAttach(target, null); + ////target.EndBatchUpdate(); + + ////Assert.NotNull(target.Child); + ////Assert.Equal(1, instantiationCount); } [Fact] @@ -702,6 +704,28 @@ namespace Avalonia.Base.UnitTests.Styling } } + [Fact] + public void DetachStyles_Should_Detach_Activator() + { + Style style = new Style(x => x.OfType().Class("foo")) + { + Setters = + { + new Setter(Class1.FooProperty, "Foo"), + }, + }; + + var target = new Class1(); + + style.TryAttach(target, null); + + Assert.Equal(1, target.Classes.ListenerCount); + + ((IStyleable)target).DetachStyles(); + + Assert.Equal(0, target.Classes.ListenerCount); + } + [Fact] public void Should_Set_Owner_On_Assigned_Resources() { diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests.cs b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests.cs index bb4d590060..cf2f946b15 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests.cs @@ -6,6 +6,7 @@ using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.LogicalTree; +using Avalonia.Media; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -35,20 +36,6 @@ namespace Avalonia.Base.UnitTests.Styling Assert.Equal(parent, target.InheritanceParent); } - [Fact] - public void Setting_Parent_Should_Not_Set_InheritanceParent_If_Already_Set() - { - var parent = new Decorator(); - var inheritanceParent = new Decorator(); - var target = new TestControl(); - - ((ISetInheritanceParent)target).SetParent(inheritanceParent); - parent.Child = target; - - Assert.Equal(parent, target.Parent); - Assert.Equal(inheritanceParent, target.InheritanceParent); - } - [Fact] public void InheritanceParent_Should_Be_Cleared_When_Removed_From_Parent() { @@ -61,20 +48,6 @@ namespace Avalonia.Base.UnitTests.Styling Assert.Null(target.InheritanceParent); } - [Fact] - public void InheritanceParent_Should_Be_Cleared_When_Removed_From_Parent_When_Has_Different_InheritanceParent() - { - var parent = new Decorator(); - var inheritanceParent = new Decorator(); - var target = new TestControl(); - - ((ISetInheritanceParent)target).SetParent(inheritanceParent); - parent.Child = target; - parent.Child = null; - - Assert.Null(target.InheritanceParent); - } - [Fact] public void Adding_Element_With_Null_Parent_To_Logical_Tree_Should_Throw() { @@ -126,7 +99,7 @@ namespace Avalonia.Base.UnitTests.Styling Assert.True(childRaised); Assert.True(grandchildRaised); } - + [Fact] public void AttachedToLogicalTree_Should_Be_Called_Before_Parent_Change_Signalled() { @@ -329,6 +302,8 @@ namespace Avalonia.Base.UnitTests.Styling var root = new TestRoot(); var child = new Border(); + AvaloniaLocator.CurrentMutable.BindToSelf(new Styler()); + root.Child = child; Assert.Throws(() => child.Name = "foo"); @@ -351,22 +326,28 @@ namespace Avalonia.Base.UnitTests.Styling } [Fact] - public void StyleInstance_Is_Disposed_When_Control_Removed_From_Logical_Tree() + public void Style_Is_Removed_When_Control_Removed_From_Logical_Tree() { - using (AvaloniaLocator.EnterScope()) + var app = UnitTestApplication.Start(TestServices.RealStyler); + var target = new Border(); + var root = new TestRoot { - var root = new TestRoot(); - var child = new Border(); - - root.Child = child; - - var styleInstance = new Mock(); - ((IStyleable)child).StyleApplied(styleInstance.Object); - - root.Child = null; + Styles = + { + new Style(x => x.OfType()) + { + Setters = + { + new Setter(Border.BackgroundProperty, Brushes.Red), + } + } + }, + Child = target, + }; - styleInstance.Verify(x => x.Dispose(), Times.Once); - } + Assert.Equal(Brushes.Red, target.Background); + root.Child = null; + Assert.Null(target.Background); } [Fact] @@ -474,7 +455,7 @@ namespace Avalonia.Base.UnitTests.Styling root.DataContext = "foo"; Assert.Equal( - new[] + new[] { "begin root", "begin a1", diff --git a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj index 5d17808e0c..6e4c54fa05 100644 --- a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj +++ b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj @@ -5,6 +5,7 @@ Exe false + diff --git a/tests/Avalonia.Benchmarks/Styling/ControlTheme_Apply.cs b/tests/Avalonia.Benchmarks/Styling/ControlTheme_Apply.cs deleted file mode 100644 index 0c9bcf412f..0000000000 --- a/tests/Avalonia.Benchmarks/Styling/ControlTheme_Apply.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Controls.Templates; -using Avalonia.Media; -using Avalonia.Styling; -using BenchmarkDotNet.Attributes; - -#nullable enable - -namespace Avalonia.Benchmarks.Styling -{ - [MemoryDiagnoser] - public class ControlTheme_Apply - { - private ControlTheme _theme; - private ControlTheme _otherTheme; - private List - + @@ -337,10 +337,6 @@ - - - - - - - A control for displaying and interacting with a data source. @@ -58,6 +42,24 @@ MinWidth="200" IsVisible="{Binding #ShowGDP.IsChecked}"/> + + + + + + + + + + + + + + @@ -70,6 +72,20 @@ + + + + + + + + diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index c23bca1810..a9f2e889b9 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -26,6 +26,7 @@ using Avalonia.Controls.Utils; using Avalonia.Layout; using Avalonia.Controls.Metadata; using Avalonia.Input.GestureRecognizers; +using Avalonia.Styling; namespace Avalonia.Controls { @@ -237,6 +238,66 @@ namespace Avalonia.Controls public static readonly StyledProperty ColumnWidthProperty = AvaloniaProperty.Register(nameof(ColumnWidth), defaultValue: DataGridLength.Auto); + /// + /// Identifies the dependency property. + /// + public static readonly StyledProperty RowThemeProperty = + AvaloniaProperty.Register(nameof(RowTheme)); + + /// + /// Gets or sets the theme applied to all rows. + /// + public ControlTheme RowTheme + { + get { return GetValue(RowThemeProperty); } + set { SetValue(RowThemeProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly StyledProperty CellThemeProperty = + AvaloniaProperty.Register(nameof(CellTheme)); + + /// + /// Gets or sets the theme applied to all cells. + /// + public ControlTheme CellTheme + { + get { return GetValue(CellThemeProperty); } + set { SetValue(CellThemeProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly StyledProperty ColumnHeaderThemeProperty = + AvaloniaProperty.Register(nameof(ColumnHeaderTheme)); + + /// + /// Gets or sets the theme applied to all column headers. + /// + public ControlTheme ColumnHeaderTheme + { + get { return GetValue(ColumnHeaderThemeProperty); } + set { SetValue(ColumnHeaderThemeProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly StyledProperty RowGroupThemeProperty = + AvaloniaProperty.Register(nameof(RowGroupTheme)); + + /// + /// Gets or sets the theme applied to all row groups. + /// + public ControlTheme RowGroupTheme + { + get { return GetValue(RowGroupThemeProperty); } + set { SetValue(RowGroupThemeProperty, value); } + } + /// /// Gets or sets the standard width or automatic sizing mode of columns in the control. /// @@ -3225,7 +3286,6 @@ namespace Avalonia.Controls } } - //TODO Styles private void AddNewCellPrivate(DataGridRow row, DataGridColumn column) { DataGridCell newCell = new DataGridCell(); @@ -3238,8 +3298,11 @@ namespace Avalonia.Controls { newCell.OwningColumn = column; newCell.IsVisible = column.IsVisible; + if (row.OwningGrid.CellTheme is {} cellTheme) + { + newCell.SetValue(ThemeProperty, cellTheme, BindingPriority.TemplatedParent); + } } - //newCell.EnsureStyle(null); row.Cells.Insert(column.Index, newCell); } @@ -4520,7 +4583,6 @@ namespace Avalonia.Controls FlushCurrentCellChanged(); } - //TODO Styles private void PopulateCellContent(bool isCellEdited, DataGridColumn dataGridColumn, DataGridRow dataGridRow, diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index fbdb979e24..191c3fc5b1 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -231,7 +231,7 @@ namespace Avalonia.Controls } /// - /// Gets or sets a value that indicates whether the user can change the column display position by + /// Gets or sets a value that indicates whether the user can change the column display position by /// dragging the column header. /// /// @@ -260,15 +260,15 @@ namespace Avalonia.Controls /// public bool CanUserResize { - get + get { return CanUserResizeInternal ?? OwningGrid?.CanUserResizeColumns ?? DataGrid.DATAGRID_defaultCanUserResizeColumns; } - set - { + set + { CanUserResizeInternal = value; OwningGrid?.OnColumnCanUserResizeChanged(this); } @@ -321,16 +321,16 @@ namespace Avalonia.Controls /// /// /// When setting this property, the specified value is less than -1 or equal to . - /// + /// /// -or- - /// + /// /// When setting this property on a column in a , the specified value is less than zero or greater than or equal to the number of columns in the . /// /// /// When setting this property, the is already making adjustments. For example, this exception is thrown when you attempt to set in a event handler. - /// + /// /// -or- - /// + /// /// When setting this property, the specified value would result in a frozen column being displayed in the range of unfrozen columns, or an unfrozen column being displayed in the range of frozen columns. /// public int DisplayIndex @@ -401,7 +401,7 @@ namespace Avalonia.Controls } } } - + /// /// Backing field for CellTheme property. /// @@ -412,7 +412,7 @@ namespace Avalonia.Controls (o, v) => o.CellTheme = v); /// - /// Gets or sets the cell theme. + /// Gets or sets the cell theme. /// public ControlTheme CellTheme { @@ -430,14 +430,14 @@ namespace Avalonia.Controls (o, v) => o.Header = v); /// - /// Gets or sets the content + /// Gets or sets the content /// public object Header { get { return _header; } set { SetAndRaise(HeaderProperty, ref _header, value); } } - + /// /// Backing field for Header property /// @@ -455,7 +455,7 @@ namespace Avalonia.Controls get { return _headerTemplate; } set { SetAndRaise(HeaderTemplateProperty, ref _headerTemplate, value); } } - + public bool IsAutoGenerated { get; @@ -750,7 +750,7 @@ namespace Avalonia.Controls protected abstract IControl GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding binding); /// - /// When overridden in a derived class, gets a read-only element that is bound to the column's + /// When overridden in a derived class, gets a read-only element that is bound to the column's /// property value. /// /// @@ -765,7 +765,7 @@ namespace Avalonia.Controls protected abstract IControl GenerateElement(DataGridCell cell, object dataItem); /// - /// Called by a specific column type when one of its properties changed, + /// Called by a specific column type when one of its properties changed, /// and its current cells need to be updated. /// /// Indicates which property changed and caused this call @@ -882,9 +882,8 @@ namespace Avalonia.Controls { LayoutRoundedWidth = ActualWidth; } - } + } - //TODO Styles internal virtual DataGridColumnHeader CreateHeader() { var result = new DataGridColumnHeader @@ -893,8 +892,10 @@ namespace Avalonia.Controls }; result[!ContentControl.ContentProperty] = this[!HeaderProperty]; result[!ContentControl.ContentTemplateProperty] = this[!HeaderTemplateProperty]; - - //result.EnsureStyle(null); + if (OwningGrid.ColumnHeaderTheme is {} columnTheme) + { + result.SetValue(StyledElement.ThemeProperty, columnTheme, BindingPriority.TemplatedParent); + } return result; } diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index d3bd968d62..740e3516f6 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -76,7 +76,7 @@ namespace Avalonia.Controls } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// //TODO Implement public DataGridColumnHeader() @@ -267,7 +267,7 @@ namespace Avalonia.Controls else { newSort = sort; - } + } // changing direction should not affect sort order, so we replace this column's // sort description instead of just adding it to the end of the collection @@ -603,7 +603,7 @@ namespace Avalonia.Controls } /// - /// Returns true if the mouse is + /// Returns true if the mouse is /// - to the left of the element, or within the left half of the element /// and /// - within the vertical range of the element, or ignoreVertical == true @@ -663,16 +663,19 @@ namespace Avalonia.Controls IsMouseOver = false; } - //TODO Styles DragIndicator private void OnMouseMove_BeginReorder(Point mousePosition) { - DataGridColumnHeader dragIndicator = new DataGridColumnHeader + var dragIndicator = new DataGridColumnHeader { OwningColumn = OwningColumn, IsEnabled = false, Content = Content, ContentTemplate = ContentTemplate }; + if (OwningGrid.ColumnHeaderTheme is {} columnHeaderTheme) + { + dragIndicator.SetValue(ThemeProperty, columnHeaderTheme, BindingPriority.TemplatedParent); + } dragIndicator.PseudoClasses.Add(":dragIndicator"); @@ -720,7 +723,7 @@ namespace Avalonia.Controls { return; } - + //handle entry into reorder mode if (_dragMode == DragMode.MouseDown && _dragColumn == null && _lastMousePositionHeaders != null && (distanceFromRight > DATAGRIDCOLUMNHEADER_resizeRegionWidth && distanceFromLeft > DATAGRIDCOLUMNHEADER_resizeRegionWidth)) { diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index 1559763a1b..cd22934ac0 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -89,7 +89,7 @@ namespace Avalonia.Controls o => o.IsValid); /// - /// Gets a value that indicates whether the data in a row is valid. + /// Gets a value that indicates whether the data in a row is valid. /// public bool IsValid { @@ -130,7 +130,7 @@ namespace Avalonia.Controls } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public DataGridRow() { @@ -240,7 +240,6 @@ namespace Avalonia.Controls private set; } - //TODO Styles internal DataGridCell FillerCell { get @@ -252,7 +251,10 @@ namespace Avalonia.Controls IsVisible = false, OwningRow = this }; - //_fillerCell.EnsureStyle(null); + if (OwningGrid.CellTheme is {} cellTheme) + { + _fillerCell.SetValue(ThemeProperty, cellTheme, BindingPriority.TemplatedParent); + } if (_cellsElement != null) { _cellsElement.Children.Add(_fillerCell); @@ -506,7 +508,7 @@ namespace Avalonia.Controls } /// - /// Measures the children of a to + /// Measures the children of a to /// prepare for arranging them during the pass. /// /// @@ -709,8 +711,6 @@ namespace Avalonia.Controls } } - // Set the proper style for the Header by walking up the Style hierarchy - //TODO Styles internal void EnsureHeaderStyleAndVisibility(Styling.Style previousStyle) { if (_headerElement != null && OwningGrid != null) @@ -785,7 +785,7 @@ namespace Avalonia.Controls OwningGrid?.OnRowDetailsChanged(); } - // Returns the actual template that should be sued for Details: either explicity set on this row + // Returns the actual template that should be sued for Details: either explicity set on this row // or inherited from the DataGrid private IDataTemplate ActualDetailsTemplate { @@ -890,7 +890,7 @@ namespace Avalonia.Controls //TODO Cleanup double? _previousDetailsHeight = null; - //TODO Animation + //TODO Animation private void DetailsContent_HeightChanged(double newValue) { if (_previousDetailsHeight.HasValue) @@ -907,7 +907,7 @@ namespace Avalonia.Controls _detailsElement.ContentHeight = newValue; - // Calling this when details are not visible invalidates during layout when we have no work + // Calling this when details are not visible invalidates during layout when we have no work // to do. In certain scenarios, this could cause a layout cycle OnRowDetailsChanged(); } @@ -1060,7 +1060,7 @@ namespace Avalonia.Controls } } } - + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { @@ -1084,7 +1084,4 @@ namespace Avalonia.Controls } } - - //TODO Styles - } diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs index 69e6766bfd..c746b19cc7 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs @@ -53,7 +53,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(PropertyName)); /// - /// Gets or sets the name of the property that this row is bound to. + /// Gets or sets the name of the property that this row is bound to. /// public string PropertyName { @@ -85,8 +85,8 @@ namespace Avalonia.Controls } /// - /// Gets or sets a value that indicates the amount that the - /// children of the are indented. + /// Gets or sets a value that indicates the amount that the + /// children of the are indented. /// public double SublevelIndent { @@ -327,9 +327,9 @@ namespace Avalonia.Controls { double xClip = Math.Round(frozenLeftEdge - childLeftEdge); var rg = new RectangleGeometry(); - rg.Rect = - new Rect(xClip, 0, - Math.Max(0, child.Bounds.Width - xClip), + rg.Rect = + new Rect(xClip, 0, + Math.Max(0, child.Bounds.Width - xClip), child.Bounds.Height); child.Clip = rg; } @@ -348,8 +348,6 @@ namespace Avalonia.Controls } } - //TODO Styles - //internal void EnsureHeaderStyleAndVisibility(Style previousStyle) internal void EnsureHeaderVisibility() { if (_headerElement != null && OwningGrid != null) diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs index f3afe2c42d..17a7eab2e0 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs @@ -14,6 +14,9 @@ using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.Linq; +using Avalonia.Data; +using Avalonia.Styling; +using JetBrains.Annotations; namespace Avalonia.Controls { @@ -117,7 +120,7 @@ namespace Avalonia.Controls detailsCount += GetDetailsCountInclusive(DisplayData.LastScrollingSlot + 1, SlotCount - 1); } - // + // double totalDetailsHeight = detailsCount * RowDetailsHeightEstimate; return totalRowsHeight + totalDetailsHeight; @@ -163,7 +166,7 @@ namespace Avalonia.Controls } /// - /// Clears the entire selection except the indicated row. Displayed rows are deselected explicitly to + /// Clears the entire selection except the indicated row. Displayed rows are deselected explicitly to /// visualize potential transition effects. The row indicated is selected if it is not already. /// internal void ClearRowSelection(int slotException, bool setAnchorSlot) @@ -270,10 +273,10 @@ namespace Avalonia.Controls bool isRow = rowIndex != -1; if (isCollapsed) { - InsertElement(slot, - element: null, + InsertElement(slot, + element: null, updateVerticalScrollBarOnly: true, - isCollapsed: true, + isCollapsed: true, isRow: isRow); } else if (SlotIsDisplayed(slot)) @@ -285,18 +288,18 @@ namespace Avalonia.Controls } else { - InsertElement(slot, GenerateRowGroupHeader(slot, groupInfo), + InsertElement(slot, GenerateRowGroupHeader(slot, groupInfo), updateVerticalScrollBarOnly: false, - isCollapsed: false, + isCollapsed: false, isRow: isRow); } } else { - InsertElement(slot, + InsertElement(slot, element: null, updateVerticalScrollBarOnly: _vScrollBar == null || _vScrollBar.IsVisible, - isCollapsed: false, + isCollapsed: false, isRow: isRow); } } @@ -417,7 +420,7 @@ namespace Avalonia.Controls if (scrolledHorizontally && DisplayData.FirstScrollingSlot <= slot && DisplayData.LastScrollingSlot >= slot) { // If the slot is displayed and we scrolled horizontally, column virtualization could cause the rows to grow. - // As a result we need to force measure on the rows we're displaying and recalculate our First and Last slots + // As a result we need to force measure on the rows we're displaying and recalculate our First and Last slots // so they're accurate foreach (DataGridRow row in DisplayData.GetScrollingRows()) { @@ -455,7 +458,7 @@ namespace Avalonia.Controls deltaY -= GetSlotElementsHeight(slot, firstFullSlot); if (DisplayData.FirstScrollingSlot - slot > 1) { - // + // ResetDisplayedRows(); } @@ -519,7 +522,7 @@ namespace Avalonia.Controls _verticalOffset = NegVerticalOffset; } - // + // Debug.Assert(MathUtilities.LessThanOrClose(NegVerticalOffset, _verticalOffset)); SetVerticalOffset(_verticalOffset); @@ -1025,6 +1028,10 @@ namespace Avalonia.Controls dataGridRow.Slot = slot; dataGridRow.OwningGrid = this; dataGridRow.DataContext = dataContext; + if (RowTheme is {} rowTheme) + { + dataGridRow.SetValue(ThemeProperty, rowTheme, BindingPriority.TemplatedParent); + } CompleteCellsCollection(dataGridRow); OnLoadingRow(new DataGridRowEventArgs(dataGridRow)); @@ -1104,7 +1111,7 @@ namespace Avalonia.Controls /// /// Checks if the row for the provided dataContext has been generated and is present - /// in either the loaded rows, pre-fetched rows, or editing row. + /// in either the loaded rows, pre-fetched rows, or editing row. /// The displayed rows are *not* searched. Returns null if the row does not belong to those 3 categories. /// private DataGridRow GetGeneratedRow(object dataContext) @@ -1152,8 +1159,8 @@ namespace Avalonia.Controls } else { - // If we're grouping, the GroupLevel needs to be fixed later by methods calling this - // which end up inserting rows. We don't do it here because elements could be inserted + // If we're grouping, the GroupLevel needs to be fixed later by methods calling this + // which end up inserting rows. We don't do it here because elements could be inserted // from top to bottom or bottom to up so it's better to do in one pass slotElement = GenerateRow(RowIndexFromSlot(slot), slot); } @@ -1161,7 +1168,6 @@ namespace Avalonia.Controls return slotElement; } - //TODO Styles private void InsertDisplayedElement(int slot, Control element, bool wasNewlyAdded, bool updateSlotInformation) { // We can only support creating new rows that are adjacent to the currently visible rows @@ -1479,7 +1485,6 @@ namespace Avalonia.Controls } } - //TODO Styles // Makes sure the row shows the proper visuals for selection, currency, details, etc. private void LoadRowVisualsForDisplay(DataGridRow row) { @@ -1694,7 +1699,7 @@ namespace Avalonia.Controls { // Figure out what row we've scrolled down to and update the value for NegVerticalOffset NegVerticalOffset = 0; - // + // if (height > 2 * CellsHeight && (RowDetailsVisibilityMode != DataGridRowDetailsVisibilityMode.VisibleWhenSelected || RowDetailsTemplate == null)) { @@ -1755,7 +1760,7 @@ namespace Avalonia.Controls // Figure out what row we've scrolled up to and update the value for NegVerticalOffset deltaY = -NegVerticalOffset; NegVerticalOffset = 0; - // + // if (height < -2 * CellsHeight && (RowDetailsVisibilityMode != DataGridRowDetailsVisibilityMode.VisibleWhenSelected || RowDetailsTemplate == null)) @@ -1813,7 +1818,7 @@ namespace Avalonia.Controls if (MathUtilities.GreaterThanOrClose(0, newVerticalOffset) && newFirstScrollingSlot != 0) { // We've scrolled to the top of the ScrollBar, automatically place the user at the very top - // of the DataGrid. If this produces very odd behavior, evaluate the RowHeight estimate. + // of the DataGrid. If this produces very odd behavior, evaluate the RowHeight estimate. // strategy. For most data, this should be unnoticeable. ResetDisplayedRows(); NegVerticalOffset = 0; @@ -1994,7 +1999,6 @@ namespace Avalonia.Controls VisibleSlotCount = 0; } - //TODO Styles private void UnloadRow(DataGridRow dataGridRow) { Debug.Assert(dataGridRow != null); @@ -2010,16 +2014,13 @@ namespace Avalonia.Controls OnUnloadingRow(new DataGridRowEventArgs(dataGridRow)); bool recycleRow = CurrentSlot != dataGridRow.Index; - // Don't recycle if the row has a custom Style set - //recycleRow &= (dataGridRow.Style == null || dataGridRow.Style == RowStyle); - if (recycleRow) { DisplayData.AddRecyclableRow(dataGridRow); } else { - // + // _rowsPresenter.Children.Remove(dataGridRow); dataGridRow.DetachFromDataGrid(false); } @@ -2240,10 +2241,10 @@ namespace Avalonia.Controls group.Items.CollectionChanged += CollectionViewGroup_CollectionChanged; } var newGroupInfo = new DataGridRowGroupInfo(group, true, parentGroupInfo.Level + 1, insertSlot, insertSlot); - InsertElementAt(insertSlot, - rowIndex: -1, - item: null, - groupInfo: newGroupInfo, + InsertElementAt(insertSlot, + rowIndex: -1, + item: null, + groupInfo: newGroupInfo, isCollapsed: isCollapsed); RowGroupHeadersTable.AddValue(insertSlot, newGroupInfo); } @@ -2256,9 +2257,9 @@ namespace Avalonia.Controls { AutoGenerateColumnsPrivate(); } - InsertElementAt(insertSlot, rowIndex, + InsertElementAt(insertSlot, rowIndex, item: e.NewItems[0], - groupInfo: null, + groupInfo: null, isCollapsed: isCollapsed); } @@ -2448,13 +2449,12 @@ namespace Avalonia.Controls VisibleSlotCount = SlotCount; } - //TODO Styles private void RefreshRowGroupHeaders() { if (DataConnection.CollectionView != null && DataConnection.CollectionView.CanGroup && DataConnection.CollectionView.Groups != null - && DataConnection.CollectionView.IsGrouping + && DataConnection.CollectionView.IsGrouping && DataConnection.CollectionView.GroupingDepth > 0) { // Initialize our array for the height of the RowGroupHeaders by Level. @@ -2476,7 +2476,7 @@ namespace Avalonia.Controls double indent; for (int i = 0; i < groupLevelCount; i++) { - indent = DATAGRID_defaultRowGroupSublevelIndent; + indent = DATAGRID_defaultRowGroupSublevelIndent; RowGroupSublevelIndents[i] = indent; if (i > 0) { @@ -2742,6 +2742,10 @@ namespace Avalonia.Controls groupHeader.RowGroupInfo = rowGroupInfo; groupHeader.DataContext = rowGroupInfo.CollectionViewGroup; groupHeader.Level = rowGroupInfo.Level; + if (RowGroupTheme is {} rowGroupTheme) + { + groupHeader.SetValue(ThemeProperty, rowGroupTheme, BindingPriority.TemplatedParent); + } // Set the RowGroupHeader's PropertyName. Unfortunately, CollectionViewGroup doesn't have this // so we have to set it manually From 5a604662cbf35c7fd630442f247fb57bd93abd5d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 14 Oct 2022 16:31:17 +0200 Subject: [PATCH 079/136] Don't use Microsoft.Reactive.Testing here. It's unsuitable for our needs as there's no way to make an observable fire on subscribe. --- .../AvaloniaObjectTests_Binding.cs | 80 +++++++------------ 1 file changed, 28 insertions(+), 52 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index e44da7de48..be7e4dc6ca 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -4,13 +4,13 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading; using System.Threading.Tasks; +using Avalonia.Base.UnitTests.Styling; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Logging; using Avalonia.Platform; using Avalonia.Threading; using Avalonia.UnitTests; -using Microsoft.Reactive.Testing; using Moq; using Xunit; @@ -489,17 +489,14 @@ namespace Avalonia.Base.UnitTests [Fact] public void Observable_Is_Unsubscribed_When_Subscription_Disposed() { - var scheduler = new TestScheduler(); - var source = scheduler.CreateColdObservable>(); + var source = new TestSubject>("foo"); var target = new Class1(); var subscription = target.Bind(Class1.FooProperty, source); - Assert.Equal(1, source.Subscriptions.Count); - Assert.Equal(Subscription.Infinite, source.Subscriptions[0].Unsubscribe); + Assert.Equal(1, source.SubscriberCount); subscription.Dispose(); - Assert.Equal(1, source.Subscriptions.Count); - Assert.Equal(0, source.Subscriptions[0].Unsubscribe); + Assert.Equal(0, source.SubscriberCount); } [Theory] @@ -508,40 +505,32 @@ namespace Avalonia.Base.UnitTests [InlineData(BindingPriority.Animation)] public void Observable_Is_Unsubscribed_When_New_Binding_Of_Same_Priority_Is_Added(BindingPriority priority) { - var scheduler = new TestScheduler(); - var source1 = scheduler.CreateColdObservable>(); - var source2 = scheduler.CreateColdObservable>(); + var source1 = new TestSubject>("foo"); + var source2 = new TestSubject>("bar"); var target = new Class1(); target.Bind(Class1.FooProperty, source1, priority); - Assert.Equal(1, source1.Subscriptions.Count); - Assert.Equal(Subscription.Infinite, source1.Subscriptions[0].Unsubscribe); + Assert.Equal(1, source1.SubscriberCount); target.Bind(Class1.FooProperty, source2, priority); - Assert.Equal(1, source2.Subscriptions.Count); - Assert.Equal(Subscription.Infinite, source2.Subscriptions[0].Unsubscribe); - Assert.Equal(1, source1.Subscriptions.Count); - Assert.Equal(0, source1.Subscriptions[0].Unsubscribe); + Assert.Equal(1, source2.SubscriberCount); + Assert.Equal(0, source1.SubscriberCount); } [Theory] [InlineData(BindingPriority.Style)] public void Observable_Is_Unsubscribed_When_New_Binding_Of_Higher_Priority_Is_Added(BindingPriority priority) { - var scheduler = new TestScheduler(); - var source1 = scheduler.CreateColdObservable>(); - var source2 = scheduler.CreateColdObservable>(); + var source1 = new TestSubject>("foo"); + var source2 = new TestSubject>("bar"); var target = new Class1(); target.Bind(Class1.FooProperty, source1, priority); - Assert.Equal(1, source1.Subscriptions.Count); - Assert.Equal(Subscription.Infinite, source1.Subscriptions[0].Unsubscribe); + Assert.Equal(1, source1.SubscriberCount); target.Bind(Class1.FooProperty, source2, priority - 1); - Assert.Equal(1, source2.Subscriptions.Count); - Assert.Equal(Subscription.Infinite, source2.Subscriptions[0].Unsubscribe); - Assert.Equal(1, source1.Subscriptions.Count); - Assert.Equal(0, source1.Subscriptions[0].Unsubscribe); + Assert.Equal(1, source2.SubscriberCount); + Assert.Equal(0, source1.SubscriberCount); } [Theory] @@ -549,34 +538,28 @@ namespace Avalonia.Base.UnitTests [InlineData(BindingPriority.Animation)] public void Observable_Is_Unsubscribed_When_New_Value_Of_Same_Priority_Is_Added(BindingPriority priority) { - var scheduler = new TestScheduler(); - var source = scheduler.CreateColdObservable>(); + var source = new TestSubject>("foo"); var target = new Class1(); target.Bind(Class1.FooProperty, source, priority); - Assert.Equal(1, source.Subscriptions.Count); - Assert.Equal(Subscription.Infinite, source.Subscriptions[0].Unsubscribe); + Assert.Equal(1, source.SubscriberCount); target.SetValue(Class1.FooProperty, "foo", priority); - Assert.Equal(1, source.Subscriptions.Count); - Assert.Equal(0, source.Subscriptions[0].Unsubscribe); + Assert.Equal(0, source.SubscriberCount); } [Theory] [InlineData(BindingPriority.Style)] public void Observable_Is_Unsubscribed_When_New_Value_Of_Higher_Priority_Is_Added(BindingPriority priority) { - var scheduler = new TestScheduler(); - var source = scheduler.CreateColdObservable>(); + var source = new TestSubject>("foo"); var target = new Class1(); target.Bind(Class1.FooProperty, source, priority); - Assert.Equal(1, source.Subscriptions.Count); - Assert.Equal(Subscription.Infinite, source.Subscriptions[0].Unsubscribe); + Assert.Equal(1, source.SubscriberCount); target.SetValue(Class1.FooProperty, "foo", priority - 1); - Assert.Equal(1, source.Subscriptions.Count); - Assert.Equal(0, source.Subscriptions[0].Unsubscribe); + Assert.Equal(0, source.SubscriberCount); } [Theory] @@ -584,36 +567,29 @@ namespace Avalonia.Base.UnitTests [InlineData(BindingPriority.Style)] public void Observable_Is_Not_Unsubscribed_When_Animation_Binding_Is_Added(BindingPriority priority) { - var scheduler = new TestScheduler(); - var source1 = scheduler.CreateColdObservable>(); - var source2 = scheduler.CreateColdObservable>(); + var source1 = new TestSubject>("foo"); + var source2 = new TestSubject>("bar"); var target = new Class1(); target.Bind(Class1.FooProperty, source1, priority); - Assert.Equal(1, source1.Subscriptions.Count); - Assert.Equal(Subscription.Infinite, source1.Subscriptions[0].Unsubscribe); + Assert.Equal(1, source1.SubscriberCount); target.Bind(Class1.FooProperty, source2, BindingPriority.Animation); - Assert.Equal(1, source2.Subscriptions.Count); - Assert.Equal(Subscription.Infinite, source2.Subscriptions[0].Unsubscribe); - Assert.Equal(1, source1.Subscriptions.Count); - Assert.Equal(Subscription.Infinite, source1.Subscriptions[0].Unsubscribe); + Assert.Equal(1, source1.SubscriberCount); + Assert.Equal(1, source2.SubscriberCount); } [Fact] public void LocalValue_Binding_Is_Not_Unsubscribed_When_LocalValue_Is_Set() { - var scheduler = new TestScheduler(); - var source = scheduler.CreateColdObservable>(); + var source = new TestSubject>("foo"); var target = new Class1(); target.Bind(Class1.FooProperty, source); - Assert.Equal(1, source.Subscriptions.Count); - Assert.Equal(Subscription.Infinite, source.Subscriptions[0].Unsubscribe); + Assert.Equal(1, source.SubscriberCount); target.SetValue(Class1.FooProperty, "foo"); - Assert.Equal(1, source.Subscriptions.Count); - Assert.Equal(Subscription.Infinite, source.Subscriptions[0].Unsubscribe); + Assert.Equal(1, source.SubscriberCount); } [Fact] From bd357e08dd0c3cfe860f2b80620e01c52f15bbad Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 14 Oct 2022 18:51:10 +0200 Subject: [PATCH 080/136] Make EffectiveValue responsible for unsubscribing. --- .../PropertyStore/BindingEntryBase.cs | 2 +- .../PropertyStore/EffectiveValue.cs | 103 ++++++--- .../PropertyStore/EffectiveValue`1.cs | 208 +++++++++--------- .../PropertyStore/ImmediateValueFrame.cs | 2 +- src/Avalonia.Base/PropertyStore/ValueStore.cs | 193 +++++----------- 5 files changed, 237 insertions(+), 271 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs index 34b8048bbc..292420ff0a 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs @@ -143,7 +143,7 @@ namespace Avalonia.PropertyStore _value = value.Value; _hasValue = true; if (_subscription is not null && _subscription != s_creatingQuiet) - Frame.Owner?.OnBindingValueChanged(Property, Frame.Priority, value.Value); + Frame.Owner?.OnBindingValueChanged(this, Frame.Priority); } } else if (value.Type != BindingValueType.DoNothing) diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs index 9ed4c67fd6..d5445b8f97 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs @@ -1,5 +1,4 @@ -using System; -using Avalonia.Data; +using Avalonia.Data; namespace Avalonia.PropertyStore { @@ -11,6 +10,9 @@ namespace Avalonia.PropertyStore /// internal abstract class EffectiveValue { + private IValueEntry? _valueEntry; + private IValueEntry? _baseValueEntry; + /// /// Gets the current effective value as a boxed value. /// @@ -33,52 +35,66 @@ namespace Avalonia.PropertyStore public BindingPriority BasePriority { get; protected set; } /// - /// Sets the value and base value, raising - /// where necessary. + /// Begins a reevaluation pass on the effective value. + /// + /// + /// Determines whether any current local value should be cleared. + /// + /// + /// This method resets the and properties + /// to Unset, pending reevaluation. + /// + public void BeginReevaluation(bool clearLocalValue = false) + { + if (clearLocalValue || Priority != BindingPriority.LocalValue) + Priority = BindingPriority.Unset; + if (clearLocalValue || BasePriority != BindingPriority.LocalValue) + BasePriority = BindingPriority.Unset; + } + + public void EndReevaluation() + { + if (Priority == BindingPriority.Unset) + { + _valueEntry?.Unsubscribe(); + _valueEntry = null; + } + + if (BasePriority == BindingPriority.Unset) + { + _baseValueEntry?.Unsubscribe(); + _baseValueEntry = null; + } + } + + /// + /// Sets the value and base value for a non-LocalValue priority, raising + /// where necessary. /// /// The associated value store. - /// The property being changed. /// The new value of the property. /// The priority of the new value. public abstract void SetAndRaise( ValueStore owner, - AvaloniaProperty property, - object? value, + IValueEntry value, BindingPriority priority); /// - /// Sets the value and base value, raising - /// where necessary. + /// Sets the value and base value for a non-LocalValue priority, raising + /// where necessary. /// /// The associated value store. - /// The property being changed. /// The new value of the property. /// The priority of the new value. /// The new base value of the property. /// The priority of the new base value. public abstract void SetAndRaise( ValueStore owner, - AvaloniaProperty property, - object? value, + IValueEntry value, BindingPriority priority, - object? baseValue, + IValueEntry baseValue, BindingPriority basePriority); - /// - /// Sets the value, raising - /// where necessary. - /// - /// The associated value store. - /// The value entry with the new value of the property. - /// The priority of the new value. - /// - /// This method does not set the base value. - /// - public abstract void SetAndRaise( - ValueStore owner, - IValueEntry entry, - BindingPriority priority); - /// /// Set the value priority, but leaves the value unchanged. /// @@ -103,6 +119,14 @@ namespace Avalonia.PropertyStore EffectiveValue? oldValue, EffectiveValue? newValue); + /// + /// Removes the current animation value and reverts to the base value, raising + /// where necessary. + /// + /// The associated value store. + /// The property being changed. + public abstract void RemoveAnimationAndRaise(ValueStore owner, AvaloniaProperty property); + /// /// Coerces the property value. /// @@ -120,5 +144,28 @@ namespace Avalonia.PropertyStore protected abstract object? GetBoxedValue(); protected abstract object? GetBoxedBaseValue(); + + protected void UpdateValueEntry(IValueEntry? entry, BindingPriority priority) + { + if (priority <= Priority && entry != _valueEntry) + { + _valueEntry?.Unsubscribe(); + _valueEntry = entry; + } + + if (priority <= BasePriority && + priority >= BindingPriority.LocalValue && + entry != _baseValueEntry) + { + _baseValueEntry?.Unsubscribe(); + _baseValueEntry = entry; + } + } + + protected void UnsubscribeValueEntries() + { + _valueEntry?.Unsubscribe(); + _baseValueEntry?.Unsubscribe(); + } } } diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index 50b11bab33..230ef27dcc 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Avalonia.Data; +using JetBrains.Annotations; namespace Avalonia.PropertyStore { @@ -60,44 +61,117 @@ namespace Avalonia.PropertyStore public override void SetAndRaise( ValueStore owner, - AvaloniaProperty property, - object? value, + IValueEntry value, BindingPriority priority) { - // `value` should already have been converted to the correct type and - // validated by this point. - SetAndRaise(owner, (StyledPropertyBase)property, (T)value!, priority); + Debug.Assert(priority != BindingPriority.LocalValue); + UpdateValueEntry(value, priority); + SetAndRaiseCore(owner, (StyledPropertyBase)value.Property, (T)value.GetValue()!, priority); } public override void SetAndRaise( ValueStore owner, - AvaloniaProperty property, - object? value, + IValueEntry value, BindingPriority priority, - object? baseValue, + IValueEntry baseValue, BindingPriority basePriority) { - SetAndRaise(owner, (StyledPropertyBase)property, (T)value!, priority, (T)baseValue!, basePriority); + Debug.Assert(priority != BindingPriority.LocalValue); + SetAndRaiseCore(owner, (StyledPropertyBase)value.Property, (T)value.GetValue()!, priority, (T)baseValue!, basePriority); } - public override void SetAndRaise( + public void SetLocalValueAndRaise( ValueStore owner, - IValueEntry entry, - BindingPriority priority) + StyledPropertyBase property, + T value) { - var value = entry is IValueEntry typed ? typed.GetValue() : (T)entry.GetValue()!; - SetAndRaise(owner, (StyledPropertyBase)entry.Property, value, priority); + SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue); } - /// - /// Sets the value and base value, raising - /// where necessary. - /// - /// The object on which to raise events. - /// The property being changed. - /// The new value of the property. - /// The priority of the new value. - public void SetAndRaise( + public bool TryGetBaseValue([MaybeNullWhen(false)] out T value) + { + value = _baseValue!; + return BasePriority != BindingPriority.Unset; + } + + public override void RaiseInheritedValueChanged( + AvaloniaObject owner, + AvaloniaProperty property, + EffectiveValue? oldValue, + EffectiveValue? newValue) + { + Debug.Assert(oldValue is not null || newValue is not null); + + var p = (StyledPropertyBase)property; + var o = oldValue is not null ? ((EffectiveValue)oldValue).Value : p.GetDefaultValue(owner.GetType()); + var n = newValue is not null ? ((EffectiveValue)newValue).Value : p.GetDefaultValue(owner.GetType()); + var priority = newValue is not null ? BindingPriority.Inherited : BindingPriority.Unset; + + if (!EqualityComparer.Default.Equals(o, n)) + { + owner.RaisePropertyChanged(p, o, n, priority, true); + } + } + + public override void RemoveAnimationAndRaise(ValueStore owner, AvaloniaProperty property) + { + Debug.Assert(Priority != BindingPriority.Animation); + Debug.Assert(BasePriority != BindingPriority.Unset); + UpdateValueEntry(null, BindingPriority.Animation); + SetAndRaiseCore(owner, (StyledPropertyBase)property, _baseValue!, BasePriority); + } + + public override void CoerceValue(ValueStore owner, AvaloniaProperty property) + { + if (_uncommon is null) + return; + SetAndRaiseCore( + owner, + (StyledPropertyBase)property, + _uncommon._uncoercedValue!, + Priority, + _uncommon._uncoercedBaseValue!, + BasePriority); + } + + public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property) + { + UnsubscribeValueEntries(); + DisposeAndRaiseUnset(owner, (StyledPropertyBase)property); + } + + public void DisposeAndRaiseUnset(ValueStore owner, StyledPropertyBase property) + { + BindingPriority priority; + T oldValue; + + if (property.Inherits && owner.TryGetInheritedValue(property, out var i)) + { + oldValue = ((EffectiveValue)i).Value; + priority = BindingPriority.Inherited; + } + else + { + oldValue = property.GetDefaultValue(owner.GetType()); + priority = BindingPriority.Unset; + } + + if (!EqualityComparer.Default.Equals(oldValue, Value)) + { + owner.Owner.RaisePropertyChanged(property, Value, oldValue, priority, true); + if (property.Inherits) + owner.OnInheritedEffectiveValueDisposed(property, Value); + } + } + + protected override object? GetBoxedValue() => Value; + + protected override object? GetBoxedBaseValue() + { + return BasePriority != BindingPriority.Unset ? _baseValue : AvaloniaProperty.UnsetValue; + } + + private void SetAndRaiseCore( ValueStore owner, StyledPropertyBase property, T value, @@ -144,17 +218,7 @@ namespace Avalonia.PropertyStore } } - /// - /// Sets the value and base value, raising - /// where necessary. - /// - /// The object on which to raise events. - /// The property being changed. - /// The new value of the property. - /// The priority of the new value. - /// The new base value of the property. - /// The priority of the new base value. - public void SetAndRaise( + private void SetAndRaiseCore( ValueStore owner, StyledPropertyBase property, T value, @@ -187,7 +251,7 @@ namespace Avalonia.PropertyStore } if (priority != BindingPriority.Unset && - (BasePriority == BindingPriority.Unset || + (BasePriority == BindingPriority.Unset || !EqualityComparer.Default.Equals(_baseValue, bv))) { _baseValue = v; @@ -212,80 +276,6 @@ namespace Avalonia.PropertyStore } } - public bool TryGetBaseValue([MaybeNullWhen(false)] out T value) - { - value = _baseValue!; - return BasePriority != BindingPriority.Unset; - } - - public override void RaiseInheritedValueChanged( - AvaloniaObject owner, - AvaloniaProperty property, - EffectiveValue? oldValue, - EffectiveValue? newValue) - { - Debug.Assert(oldValue is not null || newValue is not null); - - var p = (StyledPropertyBase)property; - var o = oldValue is not null ? ((EffectiveValue)oldValue).Value : p.GetDefaultValue(owner.GetType()); - var n = newValue is not null ? ((EffectiveValue)newValue).Value : p.GetDefaultValue(owner.GetType()); - var priority = newValue is not null ? BindingPriority.Inherited : BindingPriority.Unset; - - if (!EqualityComparer.Default.Equals(o, n)) - { - owner.RaisePropertyChanged(p, o, n, priority, true); - } - } - - public override void CoerceValue(ValueStore owner, AvaloniaProperty property) - { - if (_uncommon is null) - return; - SetAndRaise( - owner, - (StyledPropertyBase)property, - _uncommon._uncoercedValue!, - Priority, - _uncommon._uncoercedBaseValue!, - BasePriority); - } - - public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property) - { - DisposeAndRaiseUnset(owner, (StyledPropertyBase)property); - } - - public void DisposeAndRaiseUnset(ValueStore owner, StyledPropertyBase property) - { - BindingPriority priority; - T oldValue; - - if (property.Inherits && owner.TryGetInheritedValue(property, out var i)) - { - oldValue = ((EffectiveValue)i).Value; - priority = BindingPriority.Inherited; - } - else - { - oldValue = property.GetDefaultValue(owner.GetType()); - priority = BindingPriority.Unset; - } - - if (!EqualityComparer.Default.Equals(oldValue, Value)) - { - owner.Owner.RaisePropertyChanged(property, Value, oldValue, priority, true); - if (property.Inherits) - owner.OnInheritedEffectiveValueDisposed(property, Value); - } - } - - protected override object? GetBoxedValue() => Value; - - protected override object? GetBoxedBaseValue() - { - return BasePriority != BindingPriority.Unset ? _baseValue : AvaloniaProperty.UnsetValue; - } - private class UncommonFields { public Func? _coerce; diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs index 636fea7b30..1d886e7501 100644 --- a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs @@ -41,7 +41,7 @@ namespace Avalonia.PropertyStore return e; } - public IDisposable AddValue(StyledPropertyBase property, T value) + public ImmediateValueEntry AddValue(StyledPropertyBase property, T value) { var e = new ImmediateValueEntry(this, property, value); Add(e); diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 7fe0213148..903146ad2b 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -62,10 +62,7 @@ namespace Avalonia.PropertyStore var result = frame.AddBinding(property, source); if (effective is null || priority <= effective.Priority) - { result.Start(); - UnsubscribeInactiveValues(frameIndex, property); - } return result; } @@ -92,10 +89,7 @@ namespace Avalonia.PropertyStore var result = frame.AddBinding(property, source); if (effective is null || priority <= effective.Priority) - { result.Start(); - UnsubscribeInactiveValues(frameIndex, property); - } return result; } @@ -122,10 +116,7 @@ namespace Avalonia.PropertyStore var result = frame.AddBinding(property, source); if (effective is null || priority <= effective.Priority) - { result.Start(); - UnsubscribeInactiveValues(frameIndex, property); - } return result; } @@ -177,26 +168,43 @@ namespace Avalonia.PropertyStore throw new ArgumentException($"{value} is not a valid value for '{property.Name}."); } - IDisposable? result = null; - if (priority != BindingPriority.LocalValue) { var frame = GetOrCreateImmediateValueFrame(property, priority, out var frameIndex); - result = frame.AddValue(property, value); - UnsubscribeInactiveValues(frameIndex, property); - } + var result = frame.AddValue(property, value); - if (TryGetEffectiveValue(property, out var existing)) - { - var effective = (EffectiveValue)existing; - effective.SetAndRaise(this, property, value, priority); + if (TryGetEffectiveValue(property, out var existing)) + { + var effective = (EffectiveValue)existing; + effective.SetAndRaise(this, result, priority); + } + else + { + var defaultValue = property.GetDefaultValue(Owner.GetType()); + var effectiveValue = new EffectiveValue(Owner, property, defaultValue, BindingPriority.Unset); + AddEffectiveValue(property, effectiveValue); + effectiveValue.SetAndRaise(this, result, priority); + } + + return result; } else { - AddEffectiveValueAndRaise(property, value, priority); - } + if (TryGetEffectiveValue(property, out var existing)) + { + var effective = (EffectiveValue)existing; + effective.SetLocalValueAndRaise(this, property, value); + } + else + { + var defaultValue = property.GetDefaultValue(Owner.GetType()); + var effectiveValue = new EffectiveValue(Owner, property, defaultValue, BindingPriority.Unset); + AddEffectiveValue(property, effectiveValue); + effectiveValue.SetLocalValueAndRaise(this, property, value); + } - return result; + return null; + } } public object? GetValue(AvaloniaProperty property) @@ -349,40 +357,15 @@ namespace Avalonia.PropertyStore /// Called by non-LocalValue binding entries to re-evaluate the effective value when the /// binding produces a new value. /// - /// The bound property. + /// The binding entry. /// The priority of binding which produced a new value. - /// The new value. public void OnBindingValueChanged( - AvaloniaProperty property, - BindingPriority priority, - object? value) + IValueEntry entry, + BindingPriority priority) { Debug.Assert(priority != BindingPriority.LocalValue); - if (TryGetEffectiveValue(property, out var existing)) - { - if (priority <= existing.Priority) - ReevaluateEffectiveValue(property, existing); - } - else - { - AddEffectiveValueAndRaise(property, value, priority); - } - } - - /// - /// Called by non-LocalValue binding entries to re-evaluate the effective value when the - /// binding produces a new value. - /// - /// The bound property. - /// The priority of binding which produced a new value. - /// The new value. - public void OnBindingValueChanged( - StyledPropertyBase property, - BindingPriority priority, - T value) - { - Debug.Assert(priority != BindingPriority.LocalValue); + var property = entry.Property; if (TryGetEffectiveValue(property, out var existing)) { @@ -391,7 +374,7 @@ namespace Avalonia.PropertyStore } else { - AddEffectiveValueAndRaise(property, value, priority); + AddEffectiveValueAndRaise(property, entry, priority); } } @@ -413,8 +396,8 @@ namespace Avalonia.PropertyStore } /// - /// Called by a to re-evaluate the effective value when the - /// binding completes or terminates on error. + /// Called by a to re-evaluate the + /// effective value when the binding completes or terminates on error. /// /// The previously bound property. /// The frame which contained the binding. @@ -697,31 +680,14 @@ namespace Avalonia.PropertyStore /// event and notifies inheritance children if necessary . /// /// The property. - /// The property value. + /// The value entry. /// The value priority. - private void AddEffectiveValueAndRaise(AvaloniaProperty property, object? value, BindingPriority priority) + private void AddEffectiveValueAndRaise(AvaloniaProperty property, IValueEntry entry, BindingPriority priority) { Debug.Assert(priority < BindingPriority.Inherited); var effectiveValue = property.CreateEffectiveValue(Owner); AddEffectiveValue(property, effectiveValue); - effectiveValue.SetAndRaise(this, property, value, priority); - } - - /// - /// Adds a new effective value, raises the initial - /// event and notifies inheritance children if necessary . - /// - /// The property type. - /// The property. - /// The property value. - /// The value priority. - private void AddEffectiveValueAndRaise(StyledPropertyBase property, T value, BindingPriority priority) - { - Debug.Assert(priority < BindingPriority.Inherited); - var defaultValue = property.GetDefaultValue(Owner.GetType()); - var effectiveValue = new EffectiveValue(Owner, property, defaultValue, BindingPriority.Unset); - AddEffectiveValue(property, effectiveValue); - effectiveValue.SetAndRaise(this, property, value, priority); + effectiveValue.SetAndRaise(this, entry, priority); } private bool RemoveEffectiveValue(AvaloniaProperty property) @@ -796,14 +762,8 @@ namespace Avalonia.PropertyStore var generation = _frameGeneration; - // Reset all non-LocalValue effective value to Unset priority. - if (current is not null) - { - if (ignoreLocalValue || current.Priority != BindingPriority.LocalValue) - current.SetPriority(BindingPriority.Unset); - if (ignoreLocalValue || current.BasePriority != BindingPriority.LocalValue) - current.SetBasePriority(BindingPriority.Unset); - } + // Notify the existing effective value that reevaluation is starting. + current?.BeginReevaluation(ignoreLocalValue); // Iterate the frames to get the effective value. for (var i = _frames.Count - 1; i >= 0; --i) @@ -821,7 +781,16 @@ namespace Avalonia.PropertyStore return; } - if (foundEntry && entry!.HasValue) + // We're interested in the value if: + // - There is no current effective value, or + // - The value's priority is higher than the current effective value's priority, or + // - The value is a non-animation value and its priority is higher than the current + // effective value's base priority + var isRelevantPriority = current is null || + priority < current.Priority || + (priority > BindingPriority.Animation && priority < current.BasePriority); + + if (foundEntry && isRelevantPriority && entry!.HasValue) { if (current is not null) { @@ -838,19 +807,13 @@ namespace Avalonia.PropertyStore if (generation != _frameGeneration) goto restart; - if (current is not null && - current.Priority < BindingPriority.Unset && - current.BasePriority < BindingPriority.Unset) - { - // If the active state of one of the trailing frames has changed since - // the last read, then we need to re-evaluate the effective values of all - // properties. - if (UnsubscribeInactiveValues(i, property)) - ReevaluateEffectiveValues(); - return; - } + if (current?.Priority < BindingPriority.Unset && + current?.BasePriority < BindingPriority.Unset) + break; } + current?.EndReevaluation(); + if (current?.Priority == BindingPriority.Unset) { if (current.BasePriority == BindingPriority.Unset) @@ -860,7 +823,7 @@ namespace Avalonia.PropertyStore } else { - current.SetAndRaise(this, property, current.BaseValue, current.BasePriority); + current.RemoveAnimationAndRaise(this, property); } } } @@ -885,16 +848,9 @@ namespace Avalonia.PropertyStore var generation = _frameGeneration; var count = _effectiveValues.Count; - // Reset all non-LocalValue effective values to Unset priority. + // Notify the existing effective values that reevaluation is starting. for (var i = 0; i < count; ++i) - { - var e = _effectiveValues[i]; - - if (e.Priority != BindingPriority.LocalValue) - e.SetPriority(BindingPriority.Unset); - if (e.BasePriority != BindingPriority.LocalValue) - e.SetBasePriority(BindingPriority.Unset); - } + _effectiveValues[i].BeginReevaluation(); // Iterate the frames, setting and creating effective values. for (var i = _frames.Count - 1; i >= 0; --i) @@ -912,10 +868,9 @@ namespace Avalonia.PropertyStore { var entry = frame.GetEntry(j); var property = entry.Property; - EffectiveValue? effectiveValue; // Unsubscribe and skip if we already have a value/base value for this property. - if (_effectiveValues.TryGetValue(property, out effectiveValue) == true && + if (_effectiveValues.TryGetValue(property, out var effectiveValue) == true && effectiveValue.BasePriority < BindingPriority.Unset) { entry.Unsubscribe(); @@ -949,6 +904,7 @@ namespace Avalonia.PropertyStore for (var i = 0; i < count; ++i) { _effectiveValues.GetKeyValue(i, out var key, out var e); + e.EndReevaluation(); if (e.Priority == BindingPriority.Unset) { @@ -973,33 +929,6 @@ namespace Avalonia.PropertyStore } } - private bool UnsubscribeInactiveValues(int activeFrameIndex, AvaloniaProperty property) - { - var foundBaseValue = _frames[activeFrameIndex].Priority != BindingPriority.Animation; - var activeChanged = false; - - for (var i = activeFrameIndex - 1; i >= 0; --i) - { - var frame = _frames[i]; - - if (!foundBaseValue && frame.Priority > BindingPriority.Animation) - { - foundBaseValue = true; - continue; - } - - if ((foundBaseValue || frame.Priority <= BindingPriority.Animation) && - frame.TryGetEntryIfActive(property, out var entry, out var changed)) - { - if (changed && frame.EntryCount > 1) - activeChanged = true; - entry.Unsubscribe(); - } - } - - return activeChanged; - } - private bool TryGetEffectiveValue( AvaloniaProperty property, [NotNullWhen(true)] out EffectiveValue? value) From b5891a2ead2825d688f24cbb72f0dc085f73fbb8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 15 Oct 2022 00:40:35 +0200 Subject: [PATCH 081/136] Fix bug in ExpressionObserver. Ensure `rootGetter` is evaluated each time the observer is initialized; plus failing and now passing test. --- .../Data/Core/ExpressionObserver.cs | 9 ++++--- .../Core/ExpressionObserverTests_Property.cs | 27 +++++++++++++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs index e4affd97de..0c7f576da6 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs @@ -52,6 +52,7 @@ namespace Avalonia.Data.Core private static readonly object UninitializedValue = new object(); private readonly ExpressionNode _node; private object? _root; + private Func? _rootGetter; private IDisposable? _rootSubscription; private WeakReference? _value; private IReadOnlyList? _transformNodes; @@ -109,11 +110,9 @@ namespace Avalonia.Data.Core IObservable update, string? description) { - _ = rootGetter ?? throw new ArgumentNullException(nameof(rootGetter)); - Description = description; - _node = node ?? throw new ArgumentNullException(nameof(rootGetter)); - _node.Target = new WeakReference(rootGetter()); + _rootGetter = rootGetter ?? throw new ArgumentNullException(nameof(rootGetter)); + _node = node ?? throw new ArgumentNullException(nameof(node)); _root = update.Select(x => rootGetter()); } @@ -263,6 +262,8 @@ namespace Avalonia.Data.Core protected override void Initialize() { _value = null; + if (_rootGetter is not null) + _node.Target = new WeakReference(_rootGetter()); _node.Subscribe(ValueChanged); StartRoot(); } diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index a9c62a3c4a..f911048960 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs @@ -3,14 +3,13 @@ using System.Collections.Generic; using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; -using Microsoft.Reactive.Testing; +using System.Threading.Tasks; using Avalonia.Data; using Avalonia.Data.Core; +using Avalonia.Threading; using Avalonia.UnitTests; +using Microsoft.Reactive.Testing; using Xunit; -using System.Threading.Tasks; -using Avalonia.Markup.Parsers; -using Avalonia.Threading; namespace Avalonia.Base.UnitTests.Data.Core { @@ -636,7 +635,25 @@ namespace Avalonia.Base.UnitTests.Data.Core target.Subscribe(x => result.Add(x)); } - + + [Fact] + public void RootGetter_Is_Reevaluated_On_Subscribe() + { + var data = "foo"; + var target = new ExpressionObserver(() => data, new EmptyExpressionNode(), new Subject(), null); + var result = new List(); + var sub = target.Subscribe(x => result.Add(x)); + + Assert.Equal(new object[] { "foo" }, result); + + sub.Dispose(); + data = "bar"; + + target.Subscribe(x => result.Add(x)); + + Assert.Equal(new object[] { "foo", "bar" }, result); + } + public class MyViewModelBase { public object Name => "Name"; } public class MyViewModel : MyViewModelBase { public new string Name => "NewName"; } From 20d85df87c42bb92f96d06db0f73dd47cf39966c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 15 Oct 2022 12:48:15 +0200 Subject: [PATCH 082/136] Simplify unset effective value removal. --- src/Avalonia.Base/PropertyStore/ValueStore.cs | 34 +++++++------------ .../Utilities/AvaloniaPropertyDictionary.cs | 23 +++++++++---- .../PropertyStore/ValueStoreTests_Frames.cs | 2 +- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 903146ad2b..19d80adaf5 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -690,6 +690,13 @@ namespace Avalonia.PropertyStore effectiveValue.SetAndRaise(this, entry, priority); } + private void RemoveEffectiveValue(AvaloniaProperty property, int index) + { + _effectiveValues.RemoveAt(index); + if (property.Inherits && --_inheritedValueCount == 0) + OnInheritanceAncestorChanged(InheritanceAncestor); + } + private bool RemoveEffectiveValue(AvaloniaProperty property) { if (_effectiveValues.Remove(property)) @@ -869,13 +876,10 @@ namespace Avalonia.PropertyStore var entry = frame.GetEntry(j); var property = entry.Property; - // Unsubscribe and skip if we already have a value/base value for this property. - if (_effectiveValues.TryGetValue(property, out var effectiveValue) == true && + // Skip if we already have a value/base value for this property. + if (_effectiveValues.TryGetValue(property, out var effectiveValue) && effectiveValue.BasePriority < BindingPriority.Unset) - { - entry.Unsubscribe(); continue; - } if (!entry.HasValue) continue; @@ -897,30 +901,16 @@ namespace Avalonia.PropertyStore } // Remove all effective values that are still unset. - PooledList? remove = null; - - count = _effectiveValues.Count; - - for (var i = 0; i < count; ++i) + for (var i = _effectiveValues.Count - 1; i >= 0; --i) { _effectiveValues.GetKeyValue(i, out var key, out var e); e.EndReevaluation(); if (e.Priority == BindingPriority.Unset) { - remove ??= new(); - remove.Add(key); - } - } - - if (remove is not null) - { - foreach (var v in remove) - { - if (RemoveEffectiveValue(v, out var e)) - e.DisposeAndRaiseUnset(this, v); + RemoveEffectiveValue(key, i); + e.DisposeAndRaiseUnset(this, key); } - remove.Dispose(); } } finally diff --git a/src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs b/src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs index 5c7d7d8605..18c03fb031 100644 --- a/src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs +++ b/src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot; namespace Avalonia.Utilities { @@ -159,9 +160,7 @@ namespace Avalonia.Utilities { if (TryGetEntry(property.Id, out var index)) { - Array.Copy(_entries, index + 1, _entries, index, _entryCount - index - 1); - _entryCount--; - _entries[_entryCount] = default; + RemoveAt(index); return true; } @@ -183,9 +182,7 @@ namespace Avalonia.Utilities if (TryGetEntry(property.Id, out var index)) { value = _entries[index].Value; - Array.Copy(_entries, index + 1, _entries, index, _entryCount - index - 1); - _entryCount--; - _entries[_entryCount] = default; + RemoveAt(index); return true; } @@ -193,6 +190,20 @@ namespace Avalonia.Utilities return false; } + /// + /// Removes the element at the specified index from the collection. + /// + /// The index. + public void RemoveAt(int index) + { + if (_entries is null) + throw new IndexOutOfRangeException(); + + Array.Copy(_entries, index + 1, _entries, index, _entryCount - index - 1); + _entryCount--; + _entries[_entryCount] = default; + } + /// /// Attempts to add the specified key and value to the collection. /// diff --git a/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs b/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs index 0c87083f8d..48bc21dc5f 100644 --- a/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs +++ b/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs @@ -69,8 +69,8 @@ namespace Avalonia.Base.UnitTests.PropertyStore Assert.Equal(new PropertyChange[] { - new(Class1.FooProperty, "foo", "foodefault"), new(Class1.BarProperty, "bar", "bardefault"), + new(Class1.FooProperty, "foo", "foodefault"), }, result); } From 9b2d9be1faeda0cbe52488021424583b43f596fa Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 17 Oct 2022 11:02:43 +0200 Subject: [PATCH 083/136] Correctly unsubscribe animation value entries. --- .../PropertyStore/EffectiveValue.cs | 44 ++++++++++++++----- src/Avalonia.Base/PropertyStore/ValueStore.cs | 2 +- .../AvaloniaObjectTests_Binding.cs | 15 +++++++ .../AvaloniaObjectTests_OnPropertyChanged.cs | 20 +++++++++ 4 files changed, 70 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs index d5445b8f97..30f90754f5 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs @@ -1,4 +1,5 @@ -using Avalonia.Data; +using System.Diagnostics; +using Avalonia.Data; namespace Avalonia.PropertyStore { @@ -147,18 +148,41 @@ namespace Avalonia.PropertyStore protected void UpdateValueEntry(IValueEntry? entry, BindingPriority priority) { - if (priority <= Priority && entry != _valueEntry) + Debug.Assert(priority != BindingPriority.LocalValue); + + if (priority <= BindingPriority.Animation) { - _valueEntry?.Unsubscribe(); - _valueEntry = entry; + // If we've received an animation value and the current value is a non-animation + // value, then the current entry becomes our base entry. + if (Priority > BindingPriority.LocalValue && Priority < BindingPriority.Inherited) + { + Debug.Assert(_valueEntry is not null); + _baseValueEntry = _valueEntry; + _valueEntry = null; + } + + if (_valueEntry != entry) + { + _valueEntry?.Unsubscribe(); + _valueEntry = entry; + } } - - if (priority <= BasePriority && - priority >= BindingPriority.LocalValue && - entry != _baseValueEntry) + else if (Priority <= BindingPriority.Animation) { - _baseValueEntry?.Unsubscribe(); - _baseValueEntry = entry; + // We've received a non-animation value and have an active animation value, so the + // new entry becomes our base entry. + if (_baseValueEntry != entry) + { + _baseValueEntry?.Unsubscribe(); + _baseValueEntry = entry; + } + } + else if (_valueEntry != entry) + { + // Both the current value and the new value are non-animation values, so the new + // entry replaces the existing entry. + _valueEntry?.Unsubscribe(); + _valueEntry = entry; } } diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 19d80adaf5..ff1138ec1c 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -369,7 +369,7 @@ namespace Avalonia.PropertyStore if (TryGetEffectiveValue(property, out var existing)) { - if (priority <= existing.Priority) + if (priority <= existing.BasePriority) ReevaluateEffectiveValue(property, existing); } else diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index be7e4dc6ca..6db339c4cd 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -562,6 +562,21 @@ namespace Avalonia.Base.UnitTests Assert.Equal(0, source.SubscriberCount); } + [Theory] + [InlineData(BindingPriority.LocalValue)] + [InlineData(BindingPriority.Style)] + public void Observable_Is_Not_Unsubscribed_When_Animation_Value_Is_Set(BindingPriority priority) + { + var source = new TestSubject>("foo"); + var target = new Class1(); + + target.Bind(Class1.FooProperty, source, priority); + Assert.Equal(1, source.SubscriberCount); + + target.SetValue(Class1.FooProperty, "bar", BindingPriority.Animation); + Assert.Equal(1, source.SubscriberCount); + } + [Theory] [InlineData(BindingPriority.LocalValue)] [InlineData(BindingPriority.Style)] diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs index 6f34865aa1..326199b3c2 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs @@ -48,6 +48,26 @@ namespace Avalonia.Base.UnitTests Assert.False(change.IsEffectiveValueChange); } + [Fact] + public void OnPropertyChangedCore_Is_Called_On_Non_Effective_Property_Binding_Value_Change() + { + var target = new Class1(); + var source = new BehaviorSubject>("styled1"); + + target.Bind(Class1.FooProperty, source, BindingPriority.Style); + target.SetValue(Class1.FooProperty, "newvalue", BindingPriority.Animation); + source.OnNext("styled2"); + + Assert.Equal(3, target.CoreChanges.Count); + + var change = (AvaloniaPropertyChangedEventArgs)target.CoreChanges[2]; + + Assert.Equal("styled2", change.NewValue.Value); + Assert.False(change.OldValue.HasValue); + Assert.Equal(BindingPriority.Style, change.Priority); + Assert.False(change.IsEffectiveValueChange); + } + [Fact] public void OnPropertyChanged_Is_Called_Only_For_Effective_Value_Changes() { From ba4bfc36f82bc16e8d5201dbed861bc82d109545 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 17 Oct 2022 11:45:19 +0200 Subject: [PATCH 084/136] Go direct to ValueStore for untyped GetValue. Avoids a virtual call in the case of styled properties. --- src/Avalonia.Base/AvaloniaObject.cs | 10 +++++++++- src/Avalonia.Base/PropertyStore/ValueStore.cs | 2 -- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 72a3fbe925..2bac41ea00 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -206,7 +206,15 @@ namespace Avalonia /// /// The property. /// The value. - public object? GetValue(AvaloniaProperty property) => property.RouteGetValue(this); + public object? GetValue(AvaloniaProperty property) + { + _ = property ?? throw new ArgumentNullException(nameof(property)); + + if (property.IsDirect) + return property.RouteGetValue(this); + else + return _values.GetValue(property); + } /// /// Gets a value. diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index ff1138ec1c..a99955eb07 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -2,12 +2,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using Avalonia.Collections.Pooled; using Avalonia.Data; using Avalonia.Diagnostics; using Avalonia.Logging; using Avalonia.Utilities; -using JetBrains.Annotations; namespace Avalonia.PropertyStore { From e2c6444ae30bfba1d652e1a5aa7186aa4f280d16 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 17 Oct 2022 11:49:49 +0200 Subject: [PATCH 085/136] Removed unused method. --- src/Avalonia.Base/PropertyStore/ValueStore.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index a99955eb07..442dc7a72f 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -707,19 +707,6 @@ namespace Avalonia.PropertyStore return false; } - private bool RemoveEffectiveValue(AvaloniaProperty property, [NotNullWhen(true)] out EffectiveValue? result) - { - if (_effectiveValues.Remove(property, out result)) - { - if (property.Inherits && --_inheritedValueCount == 0) - OnInheritanceAncestorChanged(InheritanceAncestor); - return true; - } - - result = null; - return false; - } - private void InheritedValueChanged( AvaloniaProperty property, EffectiveValue? oldValue, From 980790705b8dab1955b20948defe661c855f6177 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 17 Oct 2022 11:56:46 +0200 Subject: [PATCH 086/136] Remove unused variables/parameters. --- src/Avalonia.Base/AvaloniaObject.cs | 5 +---- src/Avalonia.Base/PropertyStore/ValueStore.cs | 13 ++++++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 2bac41ea00..6f7f4fe3ea 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -70,13 +70,10 @@ namespace Avalonia if (_inheritanceParent != value) { - var oldParent = _inheritanceParent; - var valuestore = _values; - _inheritanceParent?.RemoveInheritanceChild(this); _inheritanceParent = value; _inheritanceParent?.AddInheritanceChild(this); - _values.SetInheritanceParent(oldParent, value); + _values.SetInheritanceParent(value); } } } diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 442dc7a72f..88ed0d73d5 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -56,7 +56,8 @@ namespace Avalonia.PropertyStore else { var effective = GetEffectiveValue(property); - var frame = GetOrCreateImmediateValueFrame(property, priority, out var frameIndex); + + var frame = GetOrCreateImmediateValueFrame(property, priority, out _); var result = frame.AddBinding(property, source); if (effective is null || priority <= effective.Priority) @@ -83,7 +84,8 @@ namespace Avalonia.PropertyStore else { var effective = GetEffectiveValue(property); - var frame = GetOrCreateImmediateValueFrame(property, priority, out var frameIndex); + + var frame = GetOrCreateImmediateValueFrame(property, priority, out _); var result = frame.AddBinding(property, source); if (effective is null || priority <= effective.Priority) @@ -110,7 +112,8 @@ namespace Avalonia.PropertyStore else { var effective = GetEffectiveValue(property); - var frame = GetOrCreateImmediateValueFrame(property, priority, out var frameIndex); + + var frame = GetOrCreateImmediateValueFrame(property, priority, out _); var result = frame.AddBinding(property, source); if (effective is null || priority <= effective.Priority) @@ -168,7 +171,7 @@ namespace Avalonia.PropertyStore if (priority != BindingPriority.LocalValue) { - var frame = GetOrCreateImmediateValueFrame(property, priority, out var frameIndex); + var frame = GetOrCreateImmediateValueFrame(property, priority, out _); var result = frame.AddValue(property, value); if (TryGetEffectiveValue(property, out var existing)) @@ -274,7 +277,7 @@ namespace Avalonia.PropertyStore return false; } - public void SetInheritanceParent(AvaloniaObject? oldParent, AvaloniaObject? newParent) + public void SetInheritanceParent(AvaloniaObject? newParent) { var values = AvaloniaPropertyDictionaryPool.Get(); var oldAncestor = InheritanceAncestor; From 63cc1a0f137c72944e49430100fdea4647d66550 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 17 Oct 2022 12:00:03 +0200 Subject: [PATCH 087/136] Remove unused methods and add docs. --- .../PropertyStore/EffectiveValue.cs | 16 ++++++---------- .../PropertyStore/EffectiveValue`1.cs | 1 - 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs index 30f90754f5..647a16179f 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs @@ -53,6 +53,12 @@ namespace Avalonia.PropertyStore BasePriority = BindingPriority.Unset; } + /// + /// Ends a reevaluation pass on the effective value. + /// + /// + /// This method unsubscribes from any unused value entries. + /// public void EndReevaluation() { if (Priority == BindingPriority.Unset) @@ -96,16 +102,6 @@ namespace Avalonia.PropertyStore IValueEntry baseValue, BindingPriority basePriority); - /// - /// Set the value priority, but leaves the value unchanged. - /// - public void SetPriority(BindingPriority priority) => Priority = BindingPriority.Unset; - - /// - /// Set the base value priority, but leaves the base value unchanged. - /// - public void SetBasePriority(BindingPriority priority) => BasePriority = BindingPriority.Unset; - /// /// Raises in response to an inherited value /// change. diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index 230ef27dcc..bc2776a5df 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Avalonia.Data; -using JetBrains.Annotations; namespace Avalonia.PropertyStore { From 034e019b365b9adef75396a001729975e6237fd9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 26 Oct 2022 11:33:10 +0200 Subject: [PATCH 088/136] Don't allocate _entries until >1 value. --- src/Avalonia.Base/PropertyStore/ValueFrame.cs | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/ValueFrame.cs b/src/Avalonia.Base/PropertyStore/ValueFrame.cs index e88affab3c..90714ec5ac 100644 --- a/src/Avalonia.Base/PropertyStore/ValueFrame.cs +++ b/src/Avalonia.Base/PropertyStore/ValueFrame.cs @@ -9,12 +9,12 @@ namespace Avalonia.PropertyStore { internal abstract class ValueFrame { - private readonly List _entries = new(); + private List? _entries; private AvaloniaPropertyDictionary _index; private ValueStore? _owner; private bool _isShared; - public int EntryCount => _entries.Count; + public int EntryCount => _index.Count; public bool IsActive => GetIsActive(out _); public ValueStore? Owner => !_isShared ? _owner : throw new AvaloniaInternalException("Cannot get owner for shared ValueFrame"); @@ -22,7 +22,7 @@ namespace Avalonia.PropertyStore public bool Contains(AvaloniaProperty property) => _index.ContainsKey(property); - public IValueEntry GetEntry(int index) => _entries[index]; + public IValueEntry GetEntry(int index) => _entries?[index] ?? _index[0]; public void SetOwner(ValueStore? owner) { @@ -51,8 +51,8 @@ namespace Avalonia.PropertyStore public virtual void Dispose() { - for (var i = 0; i < _entries.Count; ++i) - _entries[i].Unsubscribe(); + for (var i = 0; i < _index.Count; ++i) + _index[i].Unsubscribe(); } protected abstract bool GetIsActive(out bool hasChanged); @@ -66,22 +66,32 @@ namespace Avalonia.PropertyStore protected void Add(IValueEntry value) { Debug.Assert(!value.Property.IsDirect); - _entries.Add(value); + + if (_entries is null && _index.Count == 1) + { + _entries = new(); + _entries.Add(_index[0]); + } + _index.Add(value.Property, value); + _entries?.Add(value); } protected void Remove(AvaloniaProperty property) { Debug.Assert(!property.IsDirect); - var count = _entries.Count; - - for (var i = 0; i < count; ++i) + if (_entries is not null) { - if (_entries[i].Property == property) + var count = _entries.Count; + + for (var i = 0; i < count; ++i) { - _entries.RemoveAt(i); - break; + if (_entries[i].Property == property) + { + _entries.RemoveAt(i); + break; + } } } From 06efa3e0a0e1eb8ce777cb9e5b35935a53d56dc3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 26 Oct 2022 11:46:03 +0200 Subject: [PATCH 089/136] Remove unneeded interface. --- .../PropertyStore/BindingEntryBase.cs | 15 --------- .../PropertyStore/IValueEntry`1.cs | 33 ------------------- .../PropertyStore/ImmediateValueEntry.cs | 10 +----- .../SourceUntypedBindingEntry.cs | 3 +- .../PropertyStore/TypedBindingEntry.cs | 2 +- 5 files changed, 3 insertions(+), 60 deletions(-) delete mode 100644 src/Avalonia.Base/PropertyStore/IValueEntry`1.cs diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs index 292420ff0a..9df8bca447 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs @@ -58,23 +58,8 @@ namespace Avalonia.PropertyStore BindingCompleted(); } - public TValue GetValue() - { - Start(produceValue: false); - if (!_hasValue) - throw new AvaloniaInternalException("The binding entry has no value."); - return _value!; - } - public void Start() => Start(true); - public bool TryGetValue([MaybeNullWhen(false)] out TValue value) - { - Start(produceValue: false); - value = _value; - return _hasValue; - } - public void OnCompleted() => BindingCompleted(); public void OnError(Exception error) => BindingCompleted(); public void OnNext(TSource value) => SetValue(ConvertAndValidate(value)); diff --git a/src/Avalonia.Base/PropertyStore/IValueEntry`1.cs b/src/Avalonia.Base/PropertyStore/IValueEntry`1.cs deleted file mode 100644 index 7814261c91..0000000000 --- a/src/Avalonia.Base/PropertyStore/IValueEntry`1.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Avalonia.PropertyStore -{ - /// - /// Represents a typed value entry in a . - /// - internal interface IValueEntry : IValueEntry - { - /// - /// Gets the property that this value applies to. - /// - new StyledPropertyBase Property { get; } - - /// - /// Gets the value associated with the entry. - /// - /// - /// The entry has no value. - /// - new T GetValue(); - - /// - /// Tries to get the value associated with the entry. - /// - /// - /// When this method returns, contains the value associated with the entry if a value is - /// present; otherwise, returns the default value of . - /// - /// - /// true if the entry has an associated value; otherwise false. - /// - bool TryGetValue(out T? value); - } -} diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs index 7c17917393..57060f203c 100644 --- a/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs @@ -2,7 +2,7 @@ namespace Avalonia.PropertyStore { - internal class ImmediateValueEntry : IValueEntry, IDisposable + internal class ImmediateValueEntry : IValueEntry, IDisposable { private readonly ImmediateValueFrame _owner; private readonly T _value; @@ -21,14 +21,6 @@ namespace Avalonia.PropertyStore public bool HasValue => true; AvaloniaProperty IValueEntry.Property => Property; - public T GetValue() => _value; - - public bool TryGetValue(out T? value) - { - value = _value; - return true; - } - public bool TryGetValue(out object? value) { value = _value; diff --git a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs index 5ab6aa0370..b4ac06d2bf 100644 --- a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs @@ -7,8 +7,7 @@ namespace Avalonia.PropertyStore /// An that holds a binding whose source observable is untyped and /// target property is typed. /// - internal sealed class SourceUntypedBindingEntry : BindingEntryBase, - IValueEntry + internal sealed class SourceUntypedBindingEntry : BindingEntryBase { private readonly Func? _validate; diff --git a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs index f06ae9ac9f..2276991a18 100644 --- a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs @@ -7,7 +7,7 @@ namespace Avalonia.PropertyStore /// An that holds a binding whose source observable and target /// property are both typed. /// - internal sealed class TypedBindingEntry : BindingEntryBase, IValueEntry + internal sealed class TypedBindingEntry : BindingEntryBase { public TypedBindingEntry( ValueFrame frame, From e44d902374a156ed0a710ee99ecfab1696cd4ec6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 26 Oct 2022 11:53:13 +0200 Subject: [PATCH 090/136] Remove unneeded method. --- src/Avalonia.Base/PropertyStore/BindingEntryBase.cs | 7 ------- src/Avalonia.Base/PropertyStore/IValueEntry.cs | 12 ------------ .../PropertyStore/ImmediateValueEntry.cs | 6 ------ .../Styling/PropertySetterTemplateInstance.cs | 12 +----------- src/Avalonia.Base/Styling/Setter.cs | 6 ------ 5 files changed, 1 insertion(+), 42 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs index 9df8bca447..4ddab46b13 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs @@ -79,13 +79,6 @@ namespace Avalonia.PropertyStore return _value!; } - bool IValueEntry.TryGetValue(out object? value) - { - Start(produceValue: false); - value = _value; - return _hasValue; - } - protected abstract BindingValue ConvertAndValidate(TSource value); protected abstract BindingValue ConvertAndValidate(BindingValue value); diff --git a/src/Avalonia.Base/PropertyStore/IValueEntry.cs b/src/Avalonia.Base/PropertyStore/IValueEntry.cs index 3dd9497c14..271d85f8bc 100644 --- a/src/Avalonia.Base/PropertyStore/IValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/IValueEntry.cs @@ -22,18 +22,6 @@ namespace Avalonia.PropertyStore /// object? GetValue(); - /// - /// Tries to get the value associated with the entry. - /// - /// - /// When this method returns, contains the value associated with the entry if a value is - /// present; otherwise, returns null. - /// - /// - /// true if the entry has an associated value; otherwise false. - /// - bool TryGetValue(out object? value); - /// /// Called when the value entry is removed from the value store. /// diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs index 57060f203c..6040a7a328 100644 --- a/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs @@ -21,12 +21,6 @@ namespace Avalonia.PropertyStore public bool HasValue => true; AvaloniaProperty IValueEntry.Property => Property; - public bool TryGetValue(out object? value) - { - value = _value; - return true; - } - public void Unsubscribe() { } public void Dispose() => _owner.OnEntryDisposed(this); diff --git a/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs b/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs index 465dc21b57..7a39407ba2 100644 --- a/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs +++ b/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs @@ -17,17 +17,7 @@ namespace Avalonia.Styling public bool HasValue => true; public AvaloniaProperty Property { get; } - public object? GetValue() - { - TryGetValue(out var value); - return value; - } - - public bool TryGetValue(out object? value) - { - value = _value ??= _template.Build(); - return value != AvaloniaProperty.UnsetValue; - } + public object? GetValue() => _value ??= _template.Build(); void IValueEntry.Unsubscribe() { } } diff --git a/src/Avalonia.Base/Styling/Setter.cs b/src/Avalonia.Base/Styling/Setter.cs index fdee64a0de..92b35c9300 100644 --- a/src/Avalonia.Base/Styling/Setter.cs +++ b/src/Avalonia.Base/Styling/Setter.cs @@ -88,12 +88,6 @@ namespace Avalonia.Styling object? IValueEntry.GetValue() => Value; - bool IValueEntry.TryGetValue(out object? value) - { - value = Value; - return true; - } - private AvaloniaProperty EnsureProperty() { return Property ?? throw new InvalidOperationException("Setter.Property must be set."); From bfa6648441a73d6bb6404b9c2f7d70c745d67cf8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 26 Oct 2022 12:01:13 +0200 Subject: [PATCH 091/136] Remove unused code. --- .../PropertyStore/EffectiveValue.cs | 23 --------------- .../PropertyStore/EffectiveValue`1.cs | 28 +------------------ 2 files changed, 1 insertion(+), 50 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs index 647a16179f..04d3c805c2 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs @@ -19,12 +19,6 @@ namespace Avalonia.PropertyStore /// public object? Value => GetBoxedValue(); - /// - /// Gets the current effective base value as a boxed value, or - /// if not set. - /// - public object? BaseValue => GetBoxedBaseValue(); - /// /// Gets the priority of the current effective value. /// @@ -86,22 +80,6 @@ namespace Avalonia.PropertyStore IValueEntry value, BindingPriority priority); - /// - /// Sets the value and base value for a non-LocalValue priority, raising - /// where necessary. - /// - /// The associated value store. - /// The new value of the property. - /// The priority of the new value. - /// The new base value of the property. - /// The priority of the new base value. - public abstract void SetAndRaise( - ValueStore owner, - IValueEntry value, - BindingPriority priority, - IValueEntry baseValue, - BindingPriority basePriority); - /// /// Raises in response to an inherited value /// change. @@ -140,7 +118,6 @@ namespace Avalonia.PropertyStore public abstract void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property); protected abstract object? GetBoxedValue(); - protected abstract object? GetBoxedBaseValue(); protected void UpdateValueEntry(IValueEntry? entry, BindingPriority priority) { diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index bc2776a5df..343cd9465d 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -26,6 +26,7 @@ namespace Avalonia.PropertyStore { Value = value; Priority = priority; + BasePriority = BindingPriority.Unset; if (property.HasCoercion && property.GetMetadata(owner.GetType()) is { } metadata && @@ -40,17 +41,6 @@ namespace Avalonia.PropertyStore value = coerce(owner, value); } - - if (priority >= BindingPriority.LocalValue && priority < BindingPriority.Inherited) - { - _baseValue = value; - BasePriority = priority; - } - else - { - _baseValue = default; - BasePriority = BindingPriority.Unset; - } } /// @@ -68,17 +58,6 @@ namespace Avalonia.PropertyStore SetAndRaiseCore(owner, (StyledPropertyBase)value.Property, (T)value.GetValue()!, priority); } - public override void SetAndRaise( - ValueStore owner, - IValueEntry value, - BindingPriority priority, - IValueEntry baseValue, - BindingPriority basePriority) - { - Debug.Assert(priority != BindingPriority.LocalValue); - SetAndRaiseCore(owner, (StyledPropertyBase)value.Property, (T)value.GetValue()!, priority, (T)baseValue!, basePriority); - } - public void SetLocalValueAndRaise( ValueStore owner, StyledPropertyBase property, @@ -165,11 +144,6 @@ namespace Avalonia.PropertyStore protected override object? GetBoxedValue() => Value; - protected override object? GetBoxedBaseValue() - { - return BasePriority != BindingPriority.Unset ? _baseValue : AvaloniaProperty.UnsetValue; - } - private void SetAndRaiseCore( ValueStore owner, StyledPropertyBase property, From 459d195a7aa3a0ce5dc4adbb60530d17433c2239 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 26 Oct 2022 12:21:08 +0200 Subject: [PATCH 092/136] Added a few more coercion tests (1 failing). --- .../AvaloniaObjectTests_Coercion.cs | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs index 7a35fc89b7..2e1944fc76 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reactive.Subjects; using Avalonia.Data; using Xunit; @@ -52,6 +53,64 @@ namespace Avalonia.Base.UnitTests Assert.Equal(50, target.Foo); } + [Fact] + public void CoerceValue_Updates_Base_Value() + { + var target = new Class1 { Foo = 99 }; + + target.SetValue(Class1.FooProperty, 88, BindingPriority.Animation); + + Assert.Equal(88, target.Foo); + Assert.Equal(99, target.GetBaseValue(Class1.FooProperty)); + + target.MaxFoo = 50; + target.CoerceValue(Class1.FooProperty); + + Assert.Equal(50, target.Foo); + Assert.Equal(50, target.GetBaseValue(Class1.FooProperty)); + } + + [Fact] + public void CoerceValue_Raises_PropertyChanged() + { + var target = new Class1 { Foo = 99 }; + var raised = 0; + + target.PropertyChanged += (s, e) => + { + Assert.Equal(Class1.FooProperty, e.Property); + Assert.Equal(99, e.OldValue); + Assert.Equal(50, e.NewValue); + Assert.Equal(BindingPriority.LocalValue, e.Priority); + ++raised; + }; + + Assert.Equal(99, target.Foo); + + target.MaxFoo = 50; + target.CoerceValue(Class1.FooProperty); + + Assert.Equal(50, target.Foo); + Assert.Equal(1, raised); + } + + [Fact] + public void CoerceValue_Raises_PropertyChangedCore_For_Base_Value() + { + var target = new Class1 { Foo = 99 }; + + target.SetValue(Class1.FooProperty, 88, BindingPriority.Animation); + + Assert.Equal(88, target.Foo); + Assert.Equal(99, target.GetBaseValue(Class1.FooProperty)); + + target.MaxFoo = 50; + target.CoreChanges.Clear(); + target.CoerceValue(Class1.FooProperty); + + Assert.Equal(2, target.CoreChanges.Count); + } + [Fact] public void Coerced_Value_Can_Be_Restored_If_Limit_Changed() { @@ -87,6 +146,32 @@ namespace Avalonia.Base.UnitTests Assert.Equal(150, target.Foo); } + [Fact] + public void CoerceValue_Updates_Inherited_Value() + { + var parent = new Class1 { Inherited = 99 }; + var child = new AvaloniaObject { InheritanceParent = parent }; + var raised = 0; + + child.InheritanceParent = parent; + child.PropertyChanged += (s, e) => + { + Assert.Equal(Class1.InheritedProperty, e.Property); + Assert.Equal(99, e.OldValue); + Assert.Equal(50, e.NewValue); + Assert.Equal(BindingPriority.Inherited, e.Priority); + ++raised; + }; + + Assert.Equal(99, child.GetValue(Class1.InheritedProperty)); + + parent.MaxFoo = 50; + parent.CoerceValue(Class1.InheritedProperty); + + Assert.Equal(50, child.GetValue(Class1.InheritedProperty)); + Assert.Equal(1, raised); + } + [Fact] public void Coercion_Can_Be_Overridden() { @@ -111,18 +196,51 @@ namespace Avalonia.Base.UnitTests defaultValue: 11, coerce: CoerceFoo); + public static readonly StyledProperty InheritedProperty = + AvaloniaProperty.RegisterAttached( + "Attached", + defaultValue: 11, + inherits: true, + coerce: CoerceFoo); + public int Foo { get => GetValue(FooProperty); set => SetValue(FooProperty, value); } + public int Inherited + { + get => GetValue(InheritedProperty); + set => SetValue(InheritedProperty, value); + } + public int MaxFoo { get; set; } = 100; + public List CoreChanges { get; } = new(); + public static int CoerceFoo(IAvaloniaObject instance, int value) { return Math.Min(((Class1)instance).MaxFoo, value); } + + protected override void OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change) + { + CoreChanges.Add(Clone(change)); + base.OnPropertyChangedCore(change); + } + + private static AvaloniaPropertyChangedEventArgs Clone(AvaloniaPropertyChangedEventArgs change) + { + var e = (AvaloniaPropertyChangedEventArgs)change; + return new AvaloniaPropertyChangedEventArgs( + change.Sender, + e.Property, + e.OldValue, + e.NewValue, + change.Priority, + change.IsEffectiveValueChange); + } } private class Class2 : AvaloniaObject From 9bc18eff2ea1c17fadc1a80c62dcfc51bdcb0688 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 26 Oct 2022 12:23:14 +0200 Subject: [PATCH 093/136] Raise base value changed event on coercion. --- src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index 343cd9465d..3dd12b911b 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -243,7 +243,8 @@ namespace Avalonia.PropertyStore if (property.Inherits) owner.OnInheritedEffectiveValueChanged(property, oldValue, this); } - else if (baseValueChanged) + + if (baseValueChanged) { owner.Owner.RaisePropertyChanged(property, default, _baseValue!, BasePriority, false); } From d6ea0778a7da7765b187f8db2bb4c261fc25532b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Nov 2022 10:51:08 +0100 Subject: [PATCH 094/136] Make IsEvaluating re-entrancy friendly. --- src/Avalonia.Base/PropertyStore/ValueStore.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 88ed0d73d5..4da24c1414 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -15,6 +15,7 @@ namespace Avalonia.PropertyStore private Dictionary? _localValueBindings; private AvaloniaPropertyDictionary _effectiveValues; private int _inheritedValueCount; + private int _isEvaluating; private int _frameGeneration; private int _styling; @@ -22,7 +23,7 @@ namespace Avalonia.PropertyStore public AvaloniaObject Owner { get; } public ValueStore? InheritanceAncestor { get; private set; } - public bool IsEvaluating { get; private set; } + public bool IsEvaluating => _isEvaluating > 0; public IReadOnlyList Frames => _frames; public void BeginStyling() => ++_styling; @@ -745,7 +746,7 @@ namespace Avalonia.PropertyStore EffectiveValue? current, bool ignoreLocalValue = false) { - IsEvaluating = true; + ++_isEvaluating; try { @@ -824,13 +825,13 @@ namespace Avalonia.PropertyStore } finally { - IsEvaluating = false; + --_isEvaluating; } } private void ReevaluateEffectiveValues() { - IsEvaluating = true; + ++_isEvaluating; try { @@ -903,7 +904,7 @@ namespace Avalonia.PropertyStore } finally { - IsEvaluating = false; + --_isEvaluating; } } From 0cdedaaca5f8d0926fcb24a7895a2a200ad78663 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Nov 2022 11:07:15 +0100 Subject: [PATCH 095/136] Added failing transition test. --- .../Animation/AnimatableTests.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs index 294c830c91..19ae0dc260 100644 --- a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs +++ b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs @@ -442,6 +442,59 @@ namespace Avalonia.Base.UnitTests.Animation control.GetValueStore().EndStyling(); } + [Fact] + public void Transitions_Can_Be_Removed_While_Transition_In_Progress() + { + using var app = Start(); + + var opacityTransition = new DoubleTransition + { + Property = Control.OpacityProperty, + Duration = TimeSpan.FromSeconds(1), + }; + + var transitions = new Transitions { opacityTransition }; + var borderTheme = new ControlTheme(typeof(Border)) + { + Setters = + { + new Setter(Control.TransitionsProperty, transitions), + } + }; + + var clock = new TestClock(); + var root = new TestRoot + { + Clock = clock, + Resources = + { + { typeof(Border), borderTheme }, + } + }; + + var border = new Border(); + root.Child = border; + + root.LayoutManager.ExecuteInitialLayoutPass(); + + Assert.Same(transitions, border.Transitions); + + // First set property with a transition to a new value, and step the clock until + // transition is complete. + border.Opacity = 0; + clock.Step(TimeSpan.FromSeconds(0)); + clock.Step(TimeSpan.FromSeconds(1)); + Assert.Equal(0, border.Opacity); + + // Now clear the property; a transition is now in progress but no local value is + // set. + border.ClearValue(Border.OpacityProperty); + + // Remove the transition by removing the control from the logical tree. This was + // causing an exception. + root.Child = null; + } + private static IDisposable Start() { var clock = new MockGlobalClock(); From 061f01ac3a8f13563d9ac72707ea1da22d11163a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Nov 2022 11:23:58 +0100 Subject: [PATCH 096/136] Prevent exception when removing value. Calling `DisposeAndRaiseUnset` can cause a nested reevaluation of property values. --- src/Avalonia.Base/PropertyStore/ValueStore.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 4da24c1414..8d7fae41bf 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -899,6 +899,9 @@ namespace Avalonia.PropertyStore { RemoveEffectiveValue(key, i); e.DisposeAndRaiseUnset(this, key); + + if (i > _effectiveValues.Count) + break; } } } From 1440b57fb97ce7b8c264ebf570a1f2289fb74499 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Nov 2022 17:24:49 +0100 Subject: [PATCH 097/136] Add VerifyAccess check to GetBaseValue. --- src/Avalonia.Base/AvaloniaObject.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 6f7f4fe3ea..6285b44e5f 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -245,6 +245,7 @@ namespace Avalonia public Optional GetBaseValue(StyledPropertyBase property) { _ = property ?? throw new ArgumentNullException(nameof(property)); + VerifyAccess(); return _values.GetBaseValue(property); } From 2f61efaaf71be933f8ec04ceb9aa3c5306cdfb7e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Nov 2022 17:28:37 +0100 Subject: [PATCH 098/136] This method is not a public API. --- src/Avalonia.Base/AvaloniaObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 6285b44e5f..0e6e975ed9 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -616,7 +616,7 @@ namespace Avalonia /// The old property value. /// The new property value. /// The priority of the binding that produced the value. - protected void RaisePropertyChanged( + private protected void RaisePropertyChanged( DirectPropertyBase property, Optional oldValue, BindingValue newValue, From 2268f70c50a19f59fffad45aee8edb998ed75ca4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Nov 2022 17:33:15 +0100 Subject: [PATCH 099/136] Move collection polyfills to Compatibility dir. And tweak to match existing code there. --- src/Avalonia.Base/CollectionPolyfills.cs | 33 ------------------- .../CollectionCompatibilityExtensions.cs | 32 ++++++++++++++++++ 2 files changed, 32 insertions(+), 33 deletions(-) delete mode 100644 src/Avalonia.Base/CollectionPolyfills.cs create mode 100644 src/Avalonia.Base/Compatibility/CollectionCompatibilityExtensions.cs diff --git a/src/Avalonia.Base/CollectionPolyfills.cs b/src/Avalonia.Base/CollectionPolyfills.cs deleted file mode 100644 index b41c3a4d2c..0000000000 --- a/src/Avalonia.Base/CollectionPolyfills.cs +++ /dev/null @@ -1,33 +0,0 @@ -#if !NET6_0_OR_GREATER -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Avalonia -{ - internal static class CollectionPolyfills - { - public static bool Remove( - this Dictionary o, - TKey key, - [MaybeNullWhen(false)] out TValue value) - where TKey : notnull - { - if (o.TryGetValue(key, out value)) - return o.Remove(key); - return false; - } - - public static bool TryAdd(this Dictionary o, TKey key, TValue value) - where TKey : notnull - { - if (!o.ContainsKey(key)) - { - o.Add(key, value); - return true; - } - - return false; - } - } -} -#endif diff --git a/src/Avalonia.Base/Compatibility/CollectionCompatibilityExtensions.cs b/src/Avalonia.Base/Compatibility/CollectionCompatibilityExtensions.cs new file mode 100644 index 0000000000..e22288a74d --- /dev/null +++ b/src/Avalonia.Base/Compatibility/CollectionCompatibilityExtensions.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace System; + +#if !NET6_0_OR_GREATER +internal static class CollectionCompatibilityExtensions +{ + public static bool Remove( + this Dictionary o, + TKey key, + [MaybeNullWhen(false)] out TValue value) + where TKey : notnull + { + if (o.TryGetValue(key, out value)) + return o.Remove(key); + return false; + } + + public static bool TryAdd(this Dictionary o, TKey key, TValue value) + where TKey : notnull + { + if (!o.ContainsKey(key)) + { + o.Add(key, value); + return true; + } + + return false; + } +} +#endif From f332616b53ad17692dbaae6bce62d5e3e03b4a97 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Nov 2022 17:39:12 +0100 Subject: [PATCH 100/136] Make fields readonly. And remove a `using` that crept in unexpectedly. --- .../PropertyStore/AvaloniaPropertyDictionaryPool.cs | 2 +- src/Avalonia.Base/PropertyStore/BindingEntryBase.cs | 4 ++-- src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/AvaloniaPropertyDictionaryPool.cs b/src/Avalonia.Base/PropertyStore/AvaloniaPropertyDictionaryPool.cs index 2154a5741c..5a57bc3786 100644 --- a/src/Avalonia.Base/PropertyStore/AvaloniaPropertyDictionaryPool.cs +++ b/src/Avalonia.Base/PropertyStore/AvaloniaPropertyDictionaryPool.cs @@ -6,7 +6,7 @@ namespace Avalonia.PropertyStore internal static class AvaloniaPropertyDictionaryPool { private const int MaxPoolSize = 4; - private static Stack> _pool = new(); + private static readonly Stack> _pool = new(); public static AvaloniaPropertyDictionary Get() { diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs index 4ddab46b13..5b7e9adf6d 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs @@ -11,8 +11,8 @@ namespace Avalonia.PropertyStore IObserver>, IDisposable { - private static IDisposable s_creating = Disposable.Empty; - private static IDisposable s_creatingQuiet = Disposable.Create(() => { }); + private static readonly IDisposable s_creating = Disposable.Empty; + private static readonly IDisposable s_creatingQuiet = Disposable.Create(() => { }); private IDisposable? _subscription; private bool _hasValue; private TValue? _value; diff --git a/src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs b/src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs index 18c03fb031..5cac2ef658 100644 --- a/src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs +++ b/src/Avalonia.Base/Utilities/AvaloniaPropertyDictionary.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot; namespace Avalonia.Utilities { From 1184e0ef2bbc630a4293a52c02e834a1c1945f73 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Nov 2022 17:52:06 +0100 Subject: [PATCH 101/136] Remove unused operators. --- src/Avalonia.Base/Data/BindingValue.cs | 28 -------------------------- 1 file changed, 28 deletions(-) diff --git a/src/Avalonia.Base/Data/BindingValue.cs b/src/Avalonia.Base/Data/BindingValue.cs index 6f0f5696fd..4bb3ad08d5 100644 --- a/src/Avalonia.Base/Data/BindingValue.cs +++ b/src/Avalonia.Base/Data/BindingValue.cs @@ -304,34 +304,6 @@ namespace Avalonia.Data return new BindingValue(type, v, error); } - public static bool operator !=(BindingValue x, Optional y) - { - if (x.HasValue != y.HasValue) - return true; - return !EqualityComparer.Default.Equals(x.Value, y.Value); - } - - public static bool operator ==(BindingValue x, Optional y) - { - if (x.HasValue != y.HasValue) - return false; - return EqualityComparer.Default.Equals(x.Value, y.Value); - } - - public static bool operator !=(Optional x, BindingValue y) - { - if (x.HasValue != y.HasValue) - return true; - return !EqualityComparer.Default.Equals(x.Value, y.Value); - } - - public static bool operator ==(Optional x, BindingValue y) - { - if (x.HasValue != y.HasValue) - return false; - return EqualityComparer.Default.Equals(x.Value, y.Value); - } - /// /// Creates a binding value from an instance of the underlying value type. /// From ca2a0d664d7892c1879ac87ead84f1759f78efc6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Nov 2022 17:53:42 +0100 Subject: [PATCH 102/136] Make field readonly. --- src/Avalonia.Base/AvaloniaObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 0e6e975ed9..053648c8b6 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -17,11 +17,11 @@ namespace Avalonia /// public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged { + private readonly ValueStore _values; private AvaloniaObject? _inheritanceParent; private PropertyChangedEventHandler? _inpcChanged; private EventHandler? _propertyChanged; private List? _inheritanceChildren; - private ValueStore _values; /// /// Initializes a new instance of the class. From ae72b81af9733e56e50bcf74ba03c8d117c4443a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Nov 2022 23:59:06 +0100 Subject: [PATCH 103/136] Remove empty frames. In doing so, merged `ValueStore.OnBindingCompleted` into `OnValueEntryRemoved` as they were doing the same thing. --- src/Avalonia.Base/PropertyStore/ValueFrame.cs | 6 ++-- src/Avalonia.Base/PropertyStore/ValueStore.cs | 29 ++----------------- .../PropertyStore/ValueStoreTests_Frames.cs | 20 +++++++++++-- 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/ValueFrame.cs b/src/Avalonia.Base/PropertyStore/ValueFrame.cs index 90714ec5ac..5ada4b3c84 100644 --- a/src/Avalonia.Base/PropertyStore/ValueFrame.cs +++ b/src/Avalonia.Base/PropertyStore/ValueFrame.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Avalonia.Data; using Avalonia.Utilities; +using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot; namespace Avalonia.PropertyStore { @@ -45,8 +46,9 @@ namespace Avalonia.PropertyStore public void OnBindingCompleted(IValueEntry binding) { - Remove(binding.Property); - Owner?.OnBindingCompleted(binding.Property, this); + var property = binding.Property; + Remove(property); + Owner?.OnValueEntryRemoved(this, property); } public virtual void Dispose() diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 8d7fae41bf..bbc71f1a38 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -397,23 +397,6 @@ namespace Avalonia.PropertyStore } } - /// - /// Called by a to re-evaluate the - /// effective value when the binding completes or terminates on error. - /// - /// The previously bound property. - /// The frame which contained the binding. - public void OnBindingCompleted(AvaloniaProperty property, ValueFrame frame) - { - var priority = frame.Priority; - - if (TryGetEffectiveValue(property, out var existing)) - { - if (priority <= existing.Priority) - ReevaluateEffectiveValue(property, existing); - } - } - /// /// Called by a when its /// state changes. @@ -577,22 +560,14 @@ namespace Avalonia.PropertyStore /// The property whose value was removed. public void OnValueEntryRemoved(ValueFrame frame, AvaloniaProperty property) { - Debug.Assert(frame.IsActive); + if (frame.EntryCount == 0) + _frames.Remove(frame); if (TryGetEffectiveValue(property, out var existing)) { if (frame.Priority <= existing.Priority) ReevaluateEffectiveValue(property, existing); } - else - { - Logger.TryGet(LogEventLevel.Error, LogArea.Property)?.Log( - Owner, - "Internal error: ValueStore.OnEntryRemoved called for {Property} " + - "but no effective value was found.", - property); - Debug.Assert(false); - } } public bool RemoveFrame(ValueFrame frame) diff --git a/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs b/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs index 48bc21dc5f..bb726a1d63 100644 --- a/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs +++ b/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -using System.Reactive; using System.Reactive.Subjects; +using Avalonia.Data; using Avalonia.PropertyStore; using Avalonia.Styling; using Microsoft.Reactive.Testing; @@ -80,7 +80,6 @@ namespace Avalonia.Base.UnitTests.PropertyStore var target = new Class1(); var scheduler = new TestScheduler(); var obs = scheduler.CreateColdObservable(OnNext(0, "bar")); - var result = new List(); var style = new Style { Setters = @@ -99,6 +98,23 @@ namespace Avalonia.Base.UnitTests.PropertyStore Assert.NotEqual(Subscription.Infinite, obs.Subscriptions[0].Unsubscribe); } + [Fact] + public void Completing_Binding_Removes_ImmediateValueFrame() + { + var target = new Class1(); + var source = new BehaviorSubject>("foo"); + + target.Bind(Class1.FooProperty, source, BindingPriority.Animation); + + var valueStore = target.GetValueStore(); + Assert.Equal(1, valueStore.Frames.Count); + Assert.IsType(valueStore.Frames[0]); + + source.OnCompleted(); + + Assert.Equal(0, valueStore.Frames.Count); + } + private static StyleInstance InstanceStyle(Style style, StyledElement target) { var result = new StyleInstance(style, null); From fcdabffe24e3e991a993ba9776ed1f669a69fcc3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 2 Nov 2022 00:36:17 +0100 Subject: [PATCH 104/136] Signal completion even when no observers. --- .../Reactive/SingleSubscriberObservableBase.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs b/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs index 5e3a6bf79a..53a0b43c63 100644 --- a/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs +++ b/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs @@ -51,10 +51,11 @@ namespace Avalonia.Reactive protected void PublishCompleted() { + _completed = true; + if (_observer != null) { _observer.OnCompleted(); - _completed = true; Unsubscribed(); _observer = null; } @@ -62,10 +63,11 @@ namespace Avalonia.Reactive protected void PublishError(Exception error) { + _error = error; + if (_observer != null) { _observer.OnError(error); - _error = error; Unsubscribed(); _observer = null; } From 0aa7a894ef37bdf8e5f43d908528f094cff8ddcb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 2 Nov 2022 09:05:38 +0100 Subject: [PATCH 105/136] Validate priority for set/bind. --- src/Avalonia.Base/AvaloniaObject.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 053648c8b6..68c8f19f19 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Runtime.CompilerServices; using Avalonia.Data; using Avalonia.Diagnostics; using Avalonia.Logging; @@ -314,6 +315,7 @@ namespace Avalonia { _ = property ?? throw new ArgumentNullException(nameof(property)); VerifyAccess(); + ValidatePriority(priority); LogPropertySet(property, value, BindingPriority.LocalValue); @@ -378,6 +380,7 @@ namespace Avalonia property = property ?? throw new ArgumentNullException(nameof(property)); source = source ?? throw new ArgumentNullException(nameof(source)); VerifyAccess(); + ValidatePriority(priority); return _values.AddBinding(property, source, priority); } @@ -400,6 +403,7 @@ namespace Avalonia property = property ?? throw new ArgumentNullException(nameof(property)); source = source ?? throw new ArgumentNullException(nameof(source)); VerifyAccess(); + ValidatePriority(priority); return _values.AddBinding(property, source, priority); } @@ -422,6 +426,7 @@ namespace Avalonia property = property ?? throw new ArgumentNullException(nameof(property)); source = source ?? throw new ArgumentNullException(nameof(source)); VerifyAccess(); + ValidatePriority(priority); return _values.AddBinding(property, source, priority); } @@ -763,5 +768,17 @@ namespace Avalonia value, priority); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ValidatePriority(BindingPriority priority) + { + if (priority < BindingPriority.Animation || priority >= BindingPriority.Inherited) + ThrowInvalidPriority(priority); + } + + private static void ThrowInvalidPriority(BindingPriority priority) + { + throw new ArgumentException($"Invalid priority ${priority}", nameof(priority)); + } } } From a9f12cbb437ee0c5d2e9f842ef8b8095f8bed732 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 9 Nov 2022 15:48:33 +0100 Subject: [PATCH 106/136] Avoid boxing in EffectiveValue.SetAndRaise. Added `IValueEntry` interface back in and use that if present to get the value from the entry. --- .../PropertyStore/BindingEntryBase.cs | 11 +++++++++-- .../PropertyStore/EffectiveValue`1.cs | 11 ++++++++++- src/Avalonia.Base/PropertyStore/IValueEntry`1.cs | 16 ++++++++++++++++ .../PropertyStore/ImmediateValueEntry.cs | 3 ++- 4 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 src/Avalonia.Base/PropertyStore/IValueEntry`1.cs diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs index 5b7e9adf6d..ef14211902 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Reactive.Disposables; using Avalonia.Data; namespace Avalonia.PropertyStore { - internal abstract class BindingEntryBase : IValueEntry, + internal abstract class BindingEntryBase : IValueEntry, IObserver, IObserver>, IDisposable @@ -58,6 +57,14 @@ namespace Avalonia.PropertyStore BindingCompleted(); } + public TValue GetValue() + { + Start(produceValue: false); + if (!_hasValue) + throw new AvaloniaInternalException("The binding entry has no value."); + return _value!; + } + public void Start() => Start(true); public void OnCompleted() => BindingCompleted(); diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index 3dd12b911b..20d708b20e 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -55,7 +55,8 @@ namespace Avalonia.PropertyStore { Debug.Assert(priority != BindingPriority.LocalValue); UpdateValueEntry(value, priority); - SetAndRaiseCore(owner, (StyledPropertyBase)value.Property, (T)value.GetValue()!, priority); + + SetAndRaiseCore(owner, (StyledPropertyBase)value.Property, GetValue(value), priority); } public void SetLocalValueAndRaise( @@ -144,6 +145,14 @@ namespace Avalonia.PropertyStore protected override object? GetBoxedValue() => Value; + private static T GetValue(IValueEntry entry) + { + if (entry is IValueEntry typed) + return typed.GetValue(); + else + return (T)entry.GetValue()!; + } + private void SetAndRaiseCore( ValueStore owner, StyledPropertyBase property, diff --git a/src/Avalonia.Base/PropertyStore/IValueEntry`1.cs b/src/Avalonia.Base/PropertyStore/IValueEntry`1.cs new file mode 100644 index 0000000000..5b69009e79 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/IValueEntry`1.cs @@ -0,0 +1,16 @@ +namespace Avalonia.PropertyStore +{ + /// + /// Represents a typed value entry in a . + /// + internal interface IValueEntry : IValueEntry + { + /// + /// Gets the value associated with the entry. + /// + /// + /// The entry has no value. + /// + new T GetValue(); + } +} diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs index 6040a7a328..364b4e1225 100644 --- a/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs @@ -2,7 +2,7 @@ namespace Avalonia.PropertyStore { - internal class ImmediateValueEntry : IValueEntry, IDisposable + internal class ImmediateValueEntry : IValueEntry, IDisposable { private readonly ImmediateValueFrame _owner; private readonly T _value; @@ -26,5 +26,6 @@ namespace Avalonia.PropertyStore public void Dispose() => _owner.OnEntryDisposed(this); object? IValueEntry.GetValue() => _value; + T IValueEntry.GetValue() => _value; } } From 5a13453b9d5c977c55ed74bafcdfd41bb036814f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 9 Nov 2022 16:20:11 +0100 Subject: [PATCH 107/136] Cache property metadata in effective value. Decreases the number of metadata lookups needed. Previously we were doing two lookups on construction now we're only doing one. --- .../PropertyStore/EffectiveValue`1.cs | 29 ++++++++++--------- src/Avalonia.Base/PropertyStore/ValueStore.cs | 6 ++-- src/Avalonia.Base/StyledPropertyBase.cs | 2 +- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index 20d708b20e..1d8f6b8408 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -15,22 +15,19 @@ namespace Avalonia.PropertyStore /// internal sealed class EffectiveValue : EffectiveValue { + private readonly StyledPropertyMetadata _metadata; private T? _baseValue; private UncommonFields? _uncommon; - public EffectiveValue( - AvaloniaObject owner, - StyledPropertyBase property, - T value, - BindingPriority priority) + public EffectiveValue(AvaloniaObject owner, StyledPropertyBase property) { - Value = value; - Priority = priority; + Priority = BindingPriority.Unset; BasePriority = BindingPriority.Unset; + _metadata = property.GetMetadata(owner.GetType()); - if (property.HasCoercion && - property.GetMetadata(owner.GetType()) is { } metadata && - metadata.CoerceValue is { } coerce) + var value = _metadata.DefaultValue; + + if (property.HasCoercion && _metadata.CoerceValue is { } coerce) { _uncommon = new() { @@ -39,7 +36,11 @@ namespace Avalonia.PropertyStore _uncoercedBaseValue = value, }; - value = coerce(owner, value); + Value = coerce(owner, value); + } + else + { + Value = value; } } @@ -82,8 +83,8 @@ namespace Avalonia.PropertyStore Debug.Assert(oldValue is not null || newValue is not null); var p = (StyledPropertyBase)property; - var o = oldValue is not null ? ((EffectiveValue)oldValue).Value : p.GetDefaultValue(owner.GetType()); - var n = newValue is not null ? ((EffectiveValue)newValue).Value : p.GetDefaultValue(owner.GetType()); + var o = oldValue is not null ? ((EffectiveValue)oldValue).Value : _metadata.DefaultValue; + var n = newValue is not null ? ((EffectiveValue)newValue).Value : _metadata.DefaultValue; var priority = newValue is not null ? BindingPriority.Inherited : BindingPriority.Unset; if (!EqualityComparer.Default.Equals(o, n)) @@ -131,7 +132,7 @@ namespace Avalonia.PropertyStore } else { - oldValue = property.GetDefaultValue(owner.GetType()); + oldValue = _metadata.DefaultValue; priority = BindingPriority.Unset; } diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index bbc71f1a38..8790991182 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -182,8 +182,7 @@ namespace Avalonia.PropertyStore } else { - var defaultValue = property.GetDefaultValue(Owner.GetType()); - var effectiveValue = new EffectiveValue(Owner, property, defaultValue, BindingPriority.Unset); + var effectiveValue = new EffectiveValue(Owner, property); AddEffectiveValue(property, effectiveValue); effectiveValue.SetAndRaise(this, result, priority); } @@ -199,8 +198,7 @@ namespace Avalonia.PropertyStore } else { - var defaultValue = property.GetDefaultValue(Owner.GetType()); - var effectiveValue = new EffectiveValue(Owner, property, defaultValue, BindingPriority.Unset); + var effectiveValue = new EffectiveValue(Owner, property); AddEffectiveValue(property, effectiveValue); effectiveValue.SetLocalValueAndRaise(this, property, value); } diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs index 90b724bee6..6fb2f79918 100644 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ b/src/Avalonia.Base/StyledPropertyBase.cs @@ -183,7 +183,7 @@ namespace Avalonia internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o) { - return new EffectiveValue(o, this, GetDefaultValue(o.GetType()), BindingPriority.Unset); + return new EffectiveValue(o, this); } /// From a0f0e161cb088121fe2a234b3b3b607dea996615 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 16 Nov 2022 18:04:38 -0500 Subject: [PATCH 108/136] Cleanup StyleInclude and ResourceInclude in the Core project --- .../Avalonia.Markup.Xaml.csproj | 3 +- .../MarkupExtensions/StyleIncludeExtension.cs | 22 -------------- .../ResourceInclude.cs | 30 ++++++++++++------- .../Styling/StyleInclude.cs | 4 +-- 4 files changed, 23 insertions(+), 36 deletions(-) delete mode 100644 src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleIncludeExtension.cs rename src/Markup/Avalonia.Markup.Xaml/{MarkupExtensions => Styling}/ResourceInclude.cs (70%) diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 070f0c1cc3..b5ba49ce2c 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -33,9 +33,7 @@ - - @@ -45,6 +43,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleIncludeExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleIncludeExtension.cs deleted file mode 100644 index 129fa66912..0000000000 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleIncludeExtension.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Avalonia.Markup.Xaml.Styling; -using Avalonia.Styling; -using System.ComponentModel; -using System; - -namespace Avalonia.Markup.Xaml.MarkupExtensions -{ - public class StyleIncludeExtension - { - public StyleIncludeExtension() - { - } - - public IStyle ProvideValue(IServiceProvider serviceProvider) - { - return new StyleInclude(serviceProvider.GetContextBaseUri()) { Source = Source }; - } - - public Uri Source { get; set; } - - } -} diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs similarity index 70% rename from src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs rename to src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs index 1091b3ec7e..01db2c081f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs @@ -1,20 +1,37 @@ using System; -using System.ComponentModel; using Avalonia.Controls; #nullable enable -namespace Avalonia.Markup.Xaml.MarkupExtensions +namespace Avalonia.Markup.Xaml.Styling { /// /// Loads a resource dictionary from a specified URL. /// public class ResourceInclude : IResourceProvider { - private Uri? _baseUri; + private readonly Uri? _baseUri; private IResourceDictionary? _loaded; private bool _isLoading; + /// + /// Initializes a new instance of the class. + /// + /// The base URL for the XAML context. + public ResourceInclude(Uri? baseUri) + { + _baseUri = baseUri; + } + + /// + /// Initializes a new instance of the class. + /// + /// The XAML service provider. + public ResourceInclude(IServiceProvider serviceProvider) + { + _baseUri = serviceProvider.GetContextBaseUri(); + } + /// /// Gets the loaded resource dictionary. /// @@ -61,12 +78,5 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions void IResourceProvider.AddOwner(IResourceHost owner) => Loaded.AddOwner(owner); void IResourceProvider.RemoveOwner(IResourceHost owner) => Loaded.RemoveOwner(owner); - - public ResourceInclude ProvideValue(IServiceProvider serviceProvider) - { - var tdc = (ITypeDescriptorContext)serviceProvider; - _baseUri = tdc?.GetContextBaseUri(); - return this; - } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index d92003ad9f..b1725245bb 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -12,7 +12,7 @@ namespace Avalonia.Markup.Xaml.Styling /// public class StyleInclude : IStyle, IResourceProvider { - private readonly Uri _baseUri; + private readonly Uri? _baseUri; private IStyle[]? _loaded; private bool _isLoading; @@ -20,7 +20,7 @@ namespace Avalonia.Markup.Xaml.Styling /// Initializes a new instance of the class. /// /// The base URL for the XAML context. - public StyleInclude(Uri baseUri) + public StyleInclude(Uri? baseUri) { _baseUri = baseUri; } From fcf26fe4f284379a6f4f0b44484cd4f818788492 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 16 Nov 2022 18:05:50 -0500 Subject: [PATCH 109/136] Inject generated types into Ref assembly as well --- packages/Avalonia/AvaloniaBuildTasks.targets | 1 + .../CompileAvaloniaXamlTask.cs | 23 +- src/Avalonia.Build.Tasks/Program.cs | 8 +- .../XamlCompilerTaskExecutor.cs | 264 ++++++++++++++---- .../AvaloniaXamlIlCompiler.cs | 3 +- .../AvaloniaXamlIlAssetIncludeTransformer.cs | 94 +++++++ .../AvaloniaXamlIlWellKnownTypes.cs | 2 + .../XamlIl/Runtime/XamlIlRuntimeHelpers.cs | 13 + .../Avalonia.Markup.Xaml.UnitTests.csproj | 8 +- .../Xaml/BasicTests.cs | 6 +- .../Xaml/StyleTests.cs | 32 ++- 11 files changed, 380 insertions(+), 74 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 01bf303a20..4f9c4d7720 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -99,6 +99,7 @@ AssemblyFile="@(IntermediateAssembly)" ReferencesFilePath="$(AvaloniaXamlReferencesTemporaryFilePath)" OriginalCopyPath="$(AvaloniaXamlOriginalCopyFilePath)" + RefAssemblyFile="@(IntermediateRefAssembly)" ProjectDirectory="$(MSBuildProjectDirectory)" VerifyIl="$(AvaloniaXamlIlVerifyIl)" ReportImportance="$(AvaloniaXamlReportImportance)" diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs index e501fc650d..fd07e0a143 100644 --- a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs +++ b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs @@ -12,12 +12,16 @@ namespace Avalonia.Build.Tasks Enum.TryParse(ReportImportance, true, out MessageImportance outputImportance); OutputPath = OutputPath ?? AssemblyFile; + RefOutputPath = RefOutputPath ?? RefAssemblyFile; var outputPdb = GetPdbPath(OutputPath); var input = AssemblyFile; + var refInput = RefOutputPath; var inputPdb = GetPdbPath(input); - // Make a copy and delete the original file to prevent MSBuild from thinking that everything is OK + // Make a copy and delete the original file to prevent MSBuild from thinking that everything is OK if (OriginalCopyPath != null) { + var originalCopyPathRef = Path.ChangeExtension(OriginalCopyPath, ".ref.dll"); + File.Copy(AssemblyFile, OriginalCopyPath, true); input = OriginalCopyPath; File.Delete(AssemblyFile); @@ -29,14 +33,24 @@ namespace Avalonia.Build.Tasks File.Delete(inputPdb); inputPdb = copyPdb; } + + if (!string.IsNullOrWhiteSpace(RefAssemblyFile) && File.Exists(RefAssemblyFile)) + { + // We also copy ref assembly just for case if needed later for testing. + // But do not remove the original one, as MSBuild actually complains about it with multi-thread compiling. + File.Copy(RefAssemblyFile, originalCopyPathRef, true); + refInput = originalCopyPathRef; + } } var msg = $"CompileAvaloniaXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{OutputPath}"; BuildEngine.LogMessage(msg, outputImportance < MessageImportance.Low ? MessageImportance.High : outputImportance); - var res = XamlCompilerTaskExecutor.Compile(BuildEngine, input, + var res = XamlCompilerTaskExecutor.Compile(BuildEngine, + input, OutputPath, + refInput, RefOutputPath, File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(), - ProjectDirectory, OutputPath, VerifyIl, DefaultCompileBindings, outputImportance, + ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance, (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, SkipXamlCompilation, DebuggerLaunch); if (!res.Success) return false; @@ -68,6 +82,9 @@ namespace Avalonia.Build.Tasks [Required] public string ProjectDirectory { get; set; } + public string RefAssemblyFile { get; set; } + public string RefOutputPath { get; set; } + public string OutputPath { get; set; } public bool VerifyIl { get; set; } diff --git a/src/Avalonia.Build.Tasks/Program.cs b/src/Avalonia.Build.Tasks/Program.cs index f42fab5964..8ddea2d142 100644 --- a/src/Avalonia.Build.Tasks/Program.cs +++ b/src/Avalonia.Build.Tasks/Program.cs @@ -14,7 +14,7 @@ namespace Avalonia.Build.Tasks static int Main(string[] args) { - if (args.Length != 3) + if (args.Length < 3) { if (args.Length == 1) { @@ -27,11 +27,12 @@ namespace Avalonia.Build.Tasks Console.WriteLine(@$"Usage: 1) dotnet ./Avalonia.Build.Tasks.dll , where likes {referencesOutputPath} - 2) dotnet ./Avalonia.Build.Tasks.dll + 2) dotnet ./Avalonia.Build.Tasks.dll , where: - likes {referencesOutputPath}/{OriginalDll} - likes {referencesOutputPath}/{References} - - likes {referencesOutputPath}/{OutDll}"); + - likes {referencesOutputPath}/{OutDll} + - Likes {referencesOutputPath}/original.ref.dll"); return 1; } @@ -42,6 +43,7 @@ namespace Avalonia.Build.Tasks AssemblyFile = args[0], ReferencesFilePath = args[1], OutputPath = args[2], + RefAssemblyFile = args.Length > 3 ? args[3] : null, BuildEngine = new ConsoleBuildEngine(), ProjectDirectory = Directory.GetCurrentDirectory(), VerifyIl = true diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 3493fc06ed..e68cb0013a 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -1,7 +1,7 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; using Microsoft.Build.Framework; using Mono.Cecil; @@ -21,6 +21,8 @@ namespace Avalonia.Build.Tasks { public static partial class XamlCompilerTaskExecutor { + private const string CompiledAvaloniaXamlNamespace = "CompiledAvaloniaXaml"; + static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml") || r.Name.ToLowerInvariant().EndsWith(".paml") || r.Name.ToLowerInvariant().EndsWith(".axaml"); @@ -37,43 +39,58 @@ namespace Avalonia.Build.Tasks } } - public static CompileResult Compile(IBuildEngine engine, string input, string[] references, - string projectDirectory, - string output, bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, + public static CompileResult Compile(IBuildEngine engine, + string input, string output, + string refInput, string refOutput, + string[] references, string projectDirectory, + bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation) { - return Compile(engine, input, references, projectDirectory, output, verifyIl, defaultCompileBindings, logImportance, strongNameKey, skipXamlCompilation, debuggerLaunch:false); + return Compile(engine, input, output, refInput, refOutput, references, projectDirectory, verifyIl, defaultCompileBindings, logImportance, strongNameKey, skipXamlCompilation, debuggerLaunch:false); } - internal static CompileResult Compile(IBuildEngine engine, string input, string[] references, - string projectDirectory, - string output, bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation, bool debuggerLaunch) + internal static CompileResult Compile(IBuildEngine engine, + string input, string output, + string refInput, string refOutput, + string[] references, string projectDirectory, + bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation, bool debuggerLaunch) { try { - var typeSystem = new CecilTypeSystem( - references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")), - input); - - var asm = typeSystem.TargetAssemblyDefinition; - - if (!skipXamlCompilation) - { - var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, defaultCompileBindings, - logImportance, debuggerLaunch); - if (compileRes == null) - return new CompileResult(true); - if (compileRes == false) - return new CompileResult(false); - } - - var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols }; - if (!string.IsNullOrWhiteSpace(strongNameKey)) - writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey); - - asm.Write(output, writerParameters); - - return new CompileResult(true, true); + references = references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")).ToArray(); + var typeSystem = new CecilTypeSystem(references, input); + var refTypeSystem = !string.IsNullOrWhiteSpace(refInput) && File.Exists(refInput) ? new CecilTypeSystem(references, refInput) : null; + + var asm = typeSystem.TargetAssemblyDefinition; + var refAsm = refTypeSystem?.TargetAssemblyDefinition; + if (!skipXamlCompilation) + { + var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, defaultCompileBindings, logImportance, debuggerLaunch); + if (compileRes == null) + return new CompileResult(true); + if (compileRes == false) + return new CompileResult(false); + + if (refTypeSystem is not null) + { + var refCompileRes = CompileCoreForRefAssembly(engine, typeSystem, refTypeSystem); + if (refCompileRes == false) + return new CompileResult(false); + } + } + + var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols }; + if (!string.IsNullOrWhiteSpace(strongNameKey)) + writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey); + + asm.Write(output, writerParameters); + + var refWriterParameters = new WriterParameters { WriteSymbols = false }; + if (!string.IsNullOrWhiteSpace(strongNameKey)) + writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey); + refAsm?.Write(refOutput, refWriterParameters); + + return new CompileResult(true, true); } catch (Exception ex) { @@ -122,6 +139,7 @@ namespace Avalonia.Build.Tasks if (avares.Resources.Count(CheckXamlName) == 0) // Nothing to do return null; + if (typeSystem.FindType("System.Reflection.AssemblyMetadataAttribute") is {} asmMetadata) { var ctor = asm.MainModule.ImportReference(typeSystem.GetTypeReference(asmMetadata).Resolve() @@ -131,14 +149,14 @@ namespace Avalonia.Build.Tasks var arg2 = new CustomAttributeArgument(strType, defaultCompileBindings.ToString()); asm.CustomAttributes.Add(new CustomAttribute(ctor) { ConstructorArguments = { arg1, arg2 } }); } - - var clrPropertiesDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlHelpers", + + var clrPropertiesDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlHelpers", TypeAttributes.Class, asm.MainModule.TypeSystem.Object); asm.MainModule.Types.Add(clrPropertiesDef); - var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure", + var indexerAccessorClosure = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!IndexerAccessorFactoryClosure", TypeAttributes.Class, asm.MainModule.TypeSystem.Object); asm.MainModule.Types.Add(indexerAccessorClosure); - var trampolineBuilder = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlTrampolines", + var trampolineBuilder = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlTrampolines", TypeAttributes.Class, asm.MainModule.TypeSystem.Object); asm.MainModule.Types.Add(trampolineBuilder); @@ -154,7 +172,7 @@ namespace Avalonia.Build.Tasks new DeterministicIdGenerator()); - var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext", + var contextDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlContext", TypeAttributes.Class, asm.MainModule.TypeSystem.Object); asm.MainModule.Types.Add(contextDef); @@ -175,8 +193,8 @@ namespace Avalonia.Build.Tasks typeSystem.GetTypeReference(runtimeHelpers).Resolve().Methods .First(x => x.Name == "CreateRootServiceProviderV2")); - var loaderDispatcherDef = new TypeDefinition("CompiledAvaloniaXaml", "!XamlLoader", - TypeAttributes.Class, asm.MainModule.TypeSystem.Object); + var loaderDispatcherDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!XamlLoader", + TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object); loaderDispatcherDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor) @@ -204,8 +222,8 @@ namespace Avalonia.Build.Tasks bool CompileGroup(IResourceGroup group) { - var typeDef = new TypeDefinition("CompiledAvaloniaXaml", "!"+ group.Name, - TypeAttributes.Class, asm.MainModule.TypeSystem.Object); + var typeDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!"+ group.Name, + TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object); typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor) { @@ -213,7 +231,9 @@ namespace Avalonia.Build.Tasks }); asm.MainModule.Types.Add(typeDef); var builder = typeSystem.CreateTypeBuilder(typeDef); - + + var populateMethodsToTransform = new List<(MethodDefinition populateMethod, string resourceFilePath)>(); + foreach (var res in group.Resources.Where(CheckXamlName).OrderBy(x=>x.FilePath.ToLowerInvariant())) { try @@ -279,13 +299,24 @@ namespace Avalonia.Build.Tasks populateBuilder.DefineDelegateSubType(closureName, false, returnType, parameterTypes), res.Uri, res ); - - + + var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve() + .Methods.First(m => m.Name == populateName); + + // Include populate method and all nested methods/closures used in the populate method, + // So we can replace style/resource includes in all of them. + populateMethodsToTransform.Add((compiledPopulateMethod, res.FilePath)); + populateMethodsToTransform.AddRange(compiledPopulateMethod.Body.Instructions + .Where(b => b.OpCode == OpCodes.Call || b.OpCode == OpCodes.Callvirt || b.OpCode == OpCodes.Ldftn) + .Select(b => b.Operand) + .OfType() + .Where(m => compiledPopulateMethod.DeclaringType.NestedTypes.Contains(m.DeclaringType)) + .Select(m => m.Resolve()) + .Where(m => m.HasBody) + .Select(m => (m, res.FilePath))); + if (classTypeDefinition != null) { - var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve() - .Methods.First(m => m.Name == populateName); - var designLoaderFieldType = typeSystem .GetType("System.Action`1") .MakeGenericType(typeSystem.GetType("System.Object")); @@ -435,8 +466,94 @@ namespace Avalonia.Build.Tasks } res.Remove(); } - - + + foreach (var (populateMethod, resourceFilePath) in populateMethodsToTransform) + { + foreach (var instruction in populateMethod.Body.Instructions.ToArray()) + { + const string resolveStyleIncludeName = "ResolveStyleInclude"; + const string resolveResourceInclude = "ResolveResourceInclude"; + if (instruction.OpCode == OpCodes.Call + && instruction.Operand is MethodReference + { + Name: resolveStyleIncludeName or resolveResourceInclude, + DeclaringType: { Name: "XamlIlRuntimeHelpers" } + }) + { + int lineNumber = 0, linePosition = 0; + bool instructionsModified = false; + try + { + var assetSource = (string)instruction.Previous.Previous.Previous.Operand; + lineNumber = Convert.ToInt32(instruction.Previous.Previous.Operand); + linePosition = Convert.ToInt32(instruction.Previous.Operand); + + var index = populateMethod.Body.Instructions.IndexOf(instruction); + + assetSource = assetSource.Replace("avares://", ""); + + var assemblyNameSeparator = assetSource.IndexOf('/'); + var fileNameSeparator = assetSource.LastIndexOf('/'); + if (assemblyNameSeparator < 0 || fileNameSeparator < 0) + { + throw new InvalidProgramException( + $"Invalid asset source \"{assetSource}\". It must contain assembly name and relative path."); + } + + var assetAssemblyName = assetSource.Substring(0, assemblyNameSeparator); + var assetAssembly = typeSystem.FindAssembly(assetAssemblyName) + ?? throw new InvalidProgramException($"Unable to resolve assembly \"{assetAssemblyName}\""); + + var fileName = Path.GetFileNameWithoutExtension(assetSource.Replace('/', '.')); + if (assetAssembly.FindType(fileName) is { } type + && type.FindConstructor() is { } ctor) + { + var ctorMethod = + asm.MainModule.ImportReference(typeSystem.GetMethodReference(ctor)); + instructionsModified = true; + populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop); + populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop); + populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Nop); + populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Newobj, ctorMethod); + } + else + { + var resources = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources") + ?? throw new InvalidOperationException($"Unable to resolve \"!AvaloniaResources\" type on \"{assetAssemblyName}\" assembly"); + + var relativeName = "Build:" + assetSource.Substring(assemblyNameSeparator); + var buildMethod = resources.FindMethod(m => m.Name == relativeName) + ?? throw new InvalidOperationException($"Unable to resolve build method \"{relativeName}\" resource on the \"{assetAssemblyName}\" assembly"); + + var methodReference = asm.MainModule.ImportReference(typeSystem.GetMethodReference(buildMethod)); + instructionsModified = true; + populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop); + populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop); + populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Call, createRootServiceProviderMethod); + populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Call, methodReference); + } + } + catch (Exception e) + { + if (instructionsModified) + { + engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", resourceFilePath, + lineNumber, linePosition, lineNumber, linePosition, + e.Message, "", "Avalonia")); + return false; + } + else + { + engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "XAMLIL", + resourceFilePath, + lineNumber, linePosition, lineNumber, linePosition, + e.Message, "", "Avalonia")); + } + } + } + } + } + // Technically that's a hack, but it fixes corert incompatibility caused by deterministic builds int dupeCounter = 1; foreach (var grp in typeDef.NestedTypes.GroupBy(x => x.Name)) @@ -463,6 +580,55 @@ namespace Avalonia.Build.Tasks loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); return true; } - + + static bool? CompileCoreForRefAssembly( + IBuildEngine engine, CecilTypeSystem sourceTypeSystem, CecilTypeSystem refTypeSystem) + { + var asm = refTypeSystem.TargetAssemblyDefinition; + + var compiledTypes = sourceTypeSystem.TargetAssemblyDefinition.MainModule.Types + .Where(t => t.Namespace.StartsWith(CompiledAvaloniaXamlNamespace) && t.IsPublic).ToArray(); + if (compiledTypes.Length == 0) + { + return null; + } + + try + { + foreach (var ogType in compiledTypes) + { + var wrappedOgType = sourceTypeSystem.TargetAssembly.FindType(ogType.FullName); + + var clrPropertiesDef = new TypeDefinition(ogType.Namespace, ogType.Name, + TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object); + asm.MainModule.Types.Add(clrPropertiesDef); + foreach (var attribute in ogType.CustomAttributes) + { + var method = asm.MainModule.ImportReference(attribute.Constructor); + clrPropertiesDef.CustomAttributes.Add(new CustomAttribute(method, attribute.GetBlob())); + } + + var typeBuilder = refTypeSystem.CreateTypeBuilder(clrPropertiesDef); + foreach (var ogMethod in wrappedOgType.Methods.Where(m => m.IsPublic && m.IsStatic)) + { + var method = typeBuilder.DefineMethod(ogMethod.ReturnType, ogMethod.Parameters, ogMethod.Name, + ogMethod.IsPublic, ogMethod.IsStatic, false); + method.Generator.Ldnull(); + method.Generator.Throw(); + } + + typeBuilder.CreateType(); + } + } + catch (Exception e) + { + engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", "", + 0, 0, 0, 0, + e.Message, "", "Avalonia")); + return false; + } + + return true; + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 1692238d06..e601701d5c 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -56,7 +56,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions new AvaloniaXamlIlSetterTransformer(), new AvaloniaXamlIlConstructorServiceProviderTransformer(), new AvaloniaXamlIlTransitionsTypeMetadataTransformer(), - new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer() + new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(), + new AvaloniaXamlIlAssetIncludeTransformer() ); InsertBefore( new AvaloniaXamlIlOptionMarkupExtensionTransformer()); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs new file mode 100644 index 0000000000..fa878ce72c --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs @@ -0,0 +1,94 @@ +using System.Linq; +using XamlX; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; +using XamlX.Transform; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; + +internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer +{ + private const string StyleIncludeName = "StyleInclude"; + private const string ResourceIncludeName = "ResourceInclude"; + + public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) + { + if (node is not XamlAstObjectNode objectNode + || objectNode.Type.GetClrType() is not {Name: StyleIncludeName or ResourceIncludeName} objectNodeType) + { + return node; + } + + var sourceProperty = objectNode.Children.OfType().FirstOrDefault(n => n.Property.GetClrProperty().Name == "Source"); + var directives = objectNode.Children.OfType().ToList(); + if (sourceProperty is null + || objectNode.Children.Count != (directives.Count + 1)) + { + // Don't transform node with any other property, as we don't know how to transform them. + return node; + } + + if (sourceProperty.Values.OfType().FirstOrDefault() is not { } sourceTextNode) + { + // Source value can be set with markup extension instead of a text node, we don't support it here yet. + return node; + } + + var originalAssetPath = sourceTextNode.Text; + if (!(originalAssetPath.StartsWith("avares://") || originalAssetPath.StartsWith("/"))) + { + // Only "avares" protocol supported or relative paths. + return node; + } + + var runtimeHelpers = context.Configuration.TypeSystem.FindType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers"); + var markerMethodName = "Resolve" + objectNodeType.Name; + var markerMethod = runtimeHelpers.FindMethod(m => m.Name == markerMethodName && m.Parameters.Count == 3); + if (markerMethod is null) + { + throw new XamlParseException($"Marker method \"{markerMethodName}\" was not found for the \"{objectNodeType.Name}\" node", node); + } + + return new XamlValueWithManipulationNode( + node, + new AssetIncludeMethodNode(node, markerMethod, originalAssetPath), + new XamlManipulationGroupNode(node, directives)); + } + + private class AssetIncludeMethodNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode + { + private readonly IXamlMethod _method; + private readonly string _originalAssetPath; + + public AssetIncludeMethodNode( + IXamlAstNode original, IXamlMethod method, string originalAssetPath) + : base(original) + { + _method = method; + _originalAssetPath = originalAssetPath; + } + + public IXamlAstTypeReference Type => new XamlAstClrTypeReference(this, _method.ReturnType, false); + + public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen) + { + var absoluteSource = _originalAssetPath; + if (absoluteSource.StartsWith("/")) + { + // Avoid Uri class here to avoid potential problems with escaping. + // Keeping string as close to the original as possible. + var absoluteBaseUrl = context.RuntimeContext.BaseUrl; + absoluteSource = absoluteBaseUrl.Substring(0, absoluteBaseUrl.LastIndexOf('/')) + absoluteSource; + } + + codeGen.Ldstr(absoluteSource); + codeGen.Ldc_I4(Line); + codeGen.Ldc_I4(Position); + codeGen.EmitCall(_method); + + return XamlILNodeEmitResult.Type(0, _method.ReturnType); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index bf8427a129..b5a9326a7d 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -102,6 +102,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType TextDecorations { get; } public IXamlType TextTrimming { get; } public IXamlType ISetter { get; } + public IXamlType IStyle { get; } public IXamlType IResourceDictionary { get; } public IXamlType ResourceDictionary { get; } public IXamlMethod ResourceDictionaryDeferredAdd { get; } @@ -232,6 +233,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations"); TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming"); ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter"); + IStyle = cfg.TypeSystem.GetType("Avalonia.Styling.IStyle"); IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary"); ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary"); ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object, diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs index 6682532455..f547888d6e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs @@ -5,7 +5,10 @@ using System.Reflection; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Markup.Xaml.MarkupExtensions; +using Avalonia.Markup.Xaml.Styling; using Avalonia.Platform; +using Avalonia.Styling; // ReSharper disable UnusedMember.Global // ReSharper disable UnusedParameter.Global @@ -14,6 +17,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime { public static class XamlIlRuntimeHelpers { + public static IStyle ResolveStyleInclude(string absoluteSource, int line, int position) + { + return new StyleInclude((Uri)null) { Source = new Uri(absoluteSource) }.Loaded; + } + + public static IResourceDictionary ResolveResourceInclude(string absoluteSource, int line, int position) + { + return new ResourceInclude((Uri)null) { Source = new Uri(absoluteSource) }.Loaded; + } + public static Func DeferredTransformationFactoryV1(Func builder, IServiceProvider provider) { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index 7ff19e1049..bcb4bac457 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -22,12 +22,12 @@ - + Designer - - + + Designer - + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index af2435a52f..2a2e5f2478 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -473,13 +473,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.True(styles.Count == 1); - var styleInclude = styles.First() as StyleInclude; + var styleInclude = styles.First() as IStyle; Assert.NotNull(styleInclude); - - var style = styleInclude.Loaded; - - Assert.NotNull(style); } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs index 682fc622b8..e67dcfdc83 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs @@ -113,27 +113,41 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml public void StyleInclude_Is_Built() { using (UnitTestApplication.Start(TestServices.StyledWindow - .With(theme: () => new Styles()))) + .With(theme: () => new Styles()))) { var xaml = @" - + "; var window = AvaloniaRuntimeXamlLoader.Parse(xaml); + + Assert.IsType + diff --git a/src/Avalonia.Themes.Fluent/Accents/Base.xaml b/src/Avalonia.Themes.Fluent/Accents/Base.xaml index 1e2acf736d..259d107b5c 100644 --- a/src/Avalonia.Themes.Fluent/Accents/Base.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/Base.xaml @@ -1,8 +1,7 @@ - + diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml index 915be08e53..89841c92c0 100644 --- a/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml @@ -1,7 +1,5 @@ - + 12,0,12,0 + diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml index e5c0babb80..89b646fb52 100644 --- a/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml @@ -1,7 +1,5 @@ - + diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index 7e3c8673f5..71a8bc3a3c 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -1,7 +1,5 @@ - + + diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index 7917315e19..d764e1616c 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -1,7 +1,5 @@ - + diff --git a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj index 35603fe216..597fab22f8 100644 --- a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj +++ b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 5383aa3180..a029be6b8d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -1,5 +1,4 @@  diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml.cs b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml.cs deleted file mode 100644 index 37822f5c8c..0000000000 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Avalonia.Markup.Xaml; -using Avalonia.Styling; - -namespace Avalonia.Themes.Fluent.Controls -{ - /// - /// The default Avalonia theme. - /// - public class FluentControls : Styles - { - } -} diff --git a/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml b/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml index 1eb0493b08..5c48c297b8 100644 --- a/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml @@ -1,13 +1,12 @@ - diff --git a/src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml b/src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml index 3b9c5038e6..b96671b778 100644 --- a/src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml +++ b/src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml @@ -3,21 +3,19 @@ - + + 14 + 14 + 24 + 2,2,6,1 + 32 + 24 + 0,1,0,2 + 0,1,0,2 + 9,0,0,1 + 10,0,30,0 + 24 + 12,1,0,3 + 32 + diff --git a/src/Avalonia.Themes.Fluent/FluentDark.xaml b/src/Avalonia.Themes.Fluent/FluentDark.xaml deleted file mode 100644 index aad71b18fa..0000000000 --- a/src/Avalonia.Themes.Fluent/FluentDark.xaml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/src/Avalonia.Themes.Fluent/FluentLight.xaml b/src/Avalonia.Themes.Fluent/FluentLight.xaml deleted file mode 100644 index 907efe7ee6..0000000000 --- a/src/Avalonia.Themes.Fluent/FluentLight.xaml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs deleted file mode 100644 index 79dd81a068..0000000000 --- a/src/Avalonia.Themes.Fluent/FluentTheme.cs +++ /dev/null @@ -1,244 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; -using Avalonia.Markup.Xaml.Styling; -using Avalonia.Styling; - -#nullable enable - -namespace Avalonia.Themes.Fluent -{ - public enum FluentThemeMode - { - Light, - Dark, - } - - public enum DensityStyle - { - Normal, - Compact - } - - /// - /// Includes the fluent theme in an application. - /// - public class FluentTheme : AvaloniaObject, IStyle, IResourceProvider - { - private readonly Uri _baseUri; - private Styles _fluentDark = new(); - private Styles _fluentLight = new(); - private Styles _sharedStyles = new(); - private Styles _densityStyles = new(); - private bool _isLoading; - private IStyle? _loaded; - - /// - /// Initializes a new instance of the class. - /// - /// The base URL for the XAML context. - public FluentTheme(Uri baseUri) - { - _baseUri = baseUri; - InitStyles(baseUri); - } - - /// - /// Initializes a new instance of the class. - /// - /// The XAML service provider. - public FluentTheme(IServiceProvider serviceProvider) - { - var ctx = serviceProvider.GetService(typeof(IUriContext)) as IUriContext - ?? throw new NullReferenceException("Unable retrive UriContext"); - _baseUri = ctx.BaseUri; - InitStyles(_baseUri); - } - - public static readonly StyledProperty ModeProperty = - AvaloniaProperty.Register(nameof(Mode)); - - public static readonly StyledProperty DensityStyleProperty = - AvaloniaProperty.Register(nameof(DensityStyle)); - - /// - /// Gets or sets the mode of the fluent theme (light, dark). - /// - public FluentThemeMode Mode - { - get => GetValue(ModeProperty); - set => SetValue(ModeProperty, value); - } - - /// - /// Gets or sets the density style of the fluent theme (normal, compact). - /// - public DensityStyle DensityStyle - { - get => GetValue(DensityStyleProperty); - set => SetValue(DensityStyleProperty, value); - } - - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) - { - base.OnPropertyChanged(change); - - if (_loaded is null) - { - // If style wasn't yet loaded, no need to change children styles, - // it will be applied later in Loaded getter. - return; - } - - if (change.Property == ModeProperty) - { - if (Mode == FluentThemeMode.Dark) - { - (Loaded as Styles)![1] = _fluentDark[0]; - (Loaded as Styles)![2] = _fluentDark[1]; - } - else - { - (Loaded as Styles)![1] = _fluentLight[0]; - (Loaded as Styles)![2] = _fluentLight[1]; - } - } - - if (change.Property == DensityStyleProperty) - { - if (DensityStyle == DensityStyle.Compact) - { - (Loaded as Styles)!.Add(_densityStyles[0]); - } - else if (DensityStyle == DensityStyle.Normal) - { - (Loaded as Styles)!.Remove(_densityStyles[0]); - } - } - } - - public IResourceHost? Owner => (Loaded as IResourceProvider)?.Owner; - - /// - /// Gets the loaded style. - /// - public IStyle Loaded - { - get - { - if (_loaded == null) - { - _isLoading = true; - - if (Mode == FluentThemeMode.Light) - { - _loaded = new Styles() { _sharedStyles , _fluentLight[0], _fluentLight[1] }; - } - else if (Mode == FluentThemeMode.Dark) - { - _loaded = new Styles() { _sharedStyles, _fluentDark[0], _fluentDark[1] }; - } - - if (DensityStyle == DensityStyle.Compact) - { - (_loaded as Styles)!.Add(_densityStyles[0]); - } - - _isLoading = false; - } - - return _loaded!; - } - } - - bool IResourceNode.HasResources => (Loaded as IResourceProvider)?.HasResources ?? false; - - IReadOnlyList IStyle.Children => _loaded?.Children ?? Array.Empty(); - - public event EventHandler? OwnerChanged - { - add - { - if (Loaded is IResourceProvider rp) - { - rp.OwnerChanged += value; - } - } - remove - { - if (Loaded is IResourceProvider rp) - { - rp.OwnerChanged -= value; - } - } - } - - public SelectorMatchResult TryAttach(IStyleable target, object? host) => Loaded.TryAttach(target, host); - - public bool TryGetResource(object key, out object? value) - { - if (!_isLoading && Loaded is IResourceProvider p) - { - return p.TryGetResource(key, out value); - } - - value = null; - return false; - } - - void IResourceProvider.AddOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.AddOwner(owner); - void IResourceProvider.RemoveOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.RemoveOwner(owner); - - private void InitStyles(Uri baseUri) - { - _sharedStyles = new Styles - { - new StyleInclude(baseUri) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml") - }, - new StyleInclude(baseUri) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml") - }, - new StyleInclude(baseUri) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/Controls/FluentControls.xaml") - } - }; - - _fluentLight = new Styles - { - new StyleInclude(baseUri) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml") - }, - new StyleInclude(baseUri) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml") - } - }; - - _fluentDark = new Styles - { - new StyleInclude(baseUri) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml") - }, - new StyleInclude(baseUri) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml") - } - }; - - _densityStyles = new Styles - { - new StyleInclude(baseUri) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/DensityStyles/Compact.xaml") - } - }; - } - } -} diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml new file mode 100644 index 0000000000..1d710bb9dc --- /dev/null +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs new file mode 100644 index 0000000000..728e81b198 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs @@ -0,0 +1,124 @@ +using System.Collections.Generic; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; + +namespace Avalonia.Themes.Fluent +{ + public enum FluentThemeMode + { + Light, + Dark, + } + + public enum DensityStyle + { + Normal, + Compact + } + + /// + /// Includes the fluent theme in an application. + /// + public class FluentTheme : Styles + { + private readonly IResourceDictionary _baseDark; + private readonly IResourceDictionary _fluentDark; + private readonly IResourceDictionary _baseLight; + private readonly IResourceDictionary _fluentLight; + private readonly Styles _compactStyles; + + /// + /// Initializes a new instance of the class. + /// + public FluentTheme() + { + AvaloniaXamlLoader.Load(this); + + _baseDark = (IResourceDictionary)GetAndRemove("BaseDark"); + _fluentDark = (IResourceDictionary)GetAndRemove("FluentDark"); + _baseLight = (IResourceDictionary)GetAndRemove("BaseLight"); + _fluentLight = (IResourceDictionary)GetAndRemove("FluentLight"); + _compactStyles = (Styles)GetAndRemove("CompactStyles"); + + EnsureThemeVariants(); + EnsureCompactStyles(); + + object GetAndRemove(string key) + { + var val = Resources[key] + ?? throw new KeyNotFoundException($"Key {key} was not found in the resources"); + Resources.Remove(key); + return val; + } + } + + public static readonly StyledProperty ModeProperty = + AvaloniaProperty.Register(nameof(Mode)); + + public static readonly StyledProperty DensityStyleProperty = + AvaloniaProperty.Register(nameof(DensityStyle)); + + /// + /// Gets or sets the mode of the fluent theme (light, dark). + /// + public FluentThemeMode Mode + { + get => GetValue(ModeProperty); + set => SetValue(ModeProperty, value); + } + + /// + /// Gets or sets the density style of the fluent theme (normal, compact). + /// + public DensityStyle DensityStyle + { + get => GetValue(DensityStyleProperty); + set => SetValue(DensityStyleProperty, value); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ModeProperty) + { + EnsureThemeVariants(); + } + + if (change.Property == DensityStyleProperty) + { + EnsureCompactStyles(); + } + } + + private void EnsureThemeVariants() + { + var themeVariantResource1 = Mode == FluentThemeMode.Dark ? _baseDark : _baseLight; + var themeVariantResource2 = Mode == FluentThemeMode.Dark ? _fluentDark : _fluentLight; + var dict = Resources.MergedDictionaries; + if (dict.Count == 2) + { + dict.Insert(1, themeVariantResource1); + dict.Add(themeVariantResource2); + } + else + { + dict[1] = themeVariantResource1; + dict[3] = themeVariantResource2; + } + } + + private void EnsureCompactStyles() + { + if (DensityStyle == DensityStyle.Compact) + { + Add(_compactStyles); + } + else + { + Remove(_compactStyles); + } + } + } +} diff --git a/src/Avalonia.Themes.Fluent/IBitmapToImageConverter.cs b/src/Avalonia.Themes.Fluent/IBitmapToImageConverter.cs index 34670882f8..e6c46122fc 100644 --- a/src/Avalonia.Themes.Fluent/IBitmapToImageConverter.cs +++ b/src/Avalonia.Themes.Fluent/IBitmapToImageConverter.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Data.Converters; using Avalonia.Media.Imaging; @@ -12,7 +8,7 @@ namespace Avalonia.Themes.Fluent { internal class IBitmapToImageConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value != null && value is IBitmap bm) return new Image { Source=bm }; @@ -20,7 +16,7 @@ namespace Avalonia.Themes.Fluent return null; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } diff --git a/src/Avalonia.Themes.Fluent/InverseBooleanValueConverter.cs b/src/Avalonia.Themes.Fluent/InverseBooleanValueConverter.cs deleted file mode 100644 index 20bade2a67..0000000000 --- a/src/Avalonia.Themes.Fluent/InverseBooleanValueConverter.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Globalization; -using Avalonia.Data.Converters; - -namespace Avalonia.Themes.Fluent -{ - class InverseBooleanValueConverter : IValueConverter - { - public bool Default { get; set; } - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return value is bool b ? !b : Default; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - return value is bool b ? !b : !Default; - } - } -} diff --git a/src/Avalonia.Themes.Simple/Accents/Base.xaml b/src/Avalonia.Themes.Simple/Accents/Base.xaml index 1f7d703b54..bffdbd8a27 100644 --- a/src/Avalonia.Themes.Simple/Accents/Base.xaml +++ b/src/Avalonia.Themes.Simple/Accents/Base.xaml @@ -1,8 +1,7 @@ - + diff --git a/src/Avalonia.Themes.Simple/Accents/BaseDark.xaml b/src/Avalonia.Themes.Simple/Accents/BaseDark.xaml index 1843abebfd..9ad9f70c98 100644 --- a/src/Avalonia.Themes.Simple/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Simple/Accents/BaseDark.xaml @@ -1,8 +1,6 @@ - + diff --git a/src/Avalonia.Themes.Simple/Accents/BaseLight.xaml b/src/Avalonia.Themes.Simple/Accents/BaseLight.xaml index 6247815303..f96425cf06 100644 --- a/src/Avalonia.Themes.Simple/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Simple/Accents/BaseLight.xaml @@ -1,8 +1,6 @@ - + diff --git a/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj b/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj index 40ed4a0f87..e614dad4d9 100644 --- a/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj +++ b/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml b/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml index 160acd5872..ba1b35e2ee 100644 --- a/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml +++ b/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml @@ -1,14 +1,12 @@ - - diff --git a/src/Avalonia.Themes.Simple/IBitmapToImageConverter.cs b/src/Avalonia.Themes.Simple/IBitmapToImageConverter.cs index fade026b51..d5bf003288 100644 --- a/src/Avalonia.Themes.Simple/IBitmapToImageConverter.cs +++ b/src/Avalonia.Themes.Simple/IBitmapToImageConverter.cs @@ -12,7 +12,7 @@ namespace Avalonia.Themes.Simple { internal class IBitmapToImageConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value != null && value is IBitmap bm) return new Image { Source=bm }; @@ -20,7 +20,7 @@ namespace Avalonia.Themes.Simple return null; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } diff --git a/src/Avalonia.Themes.Simple/InverseBooleanValueConverter.cs b/src/Avalonia.Themes.Simple/InverseBooleanValueConverter.cs deleted file mode 100644 index 15cc5b4a80..0000000000 --- a/src/Avalonia.Themes.Simple/InverseBooleanValueConverter.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Globalization; -using Avalonia.Data.Converters; - -namespace Avalonia.Themes.Simple -{ - class InverseBooleanValueConverter : IValueConverter - { - public bool Default { get; set; } - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return value is bool b ? !b : Default; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - return value is bool b ? !b : !Default; - } - } -} diff --git a/src/Avalonia.Themes.Simple/SimpleTheme.cs b/src/Avalonia.Themes.Simple/SimpleTheme.cs deleted file mode 100644 index f225cb4c4d..0000000000 --- a/src/Avalonia.Themes.Simple/SimpleTheme.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; -using Avalonia.Markup.Xaml.Styling; -using Avalonia.Styling; -#nullable enable - -namespace Avalonia.Themes.Simple -{ - public class SimpleTheme : AvaloniaObject, IStyle, IResourceProvider - { - public static readonly StyledProperty ModeProperty = - AvaloniaProperty.Register(nameof(Mode)); - - private readonly Uri _baseUri; - private bool _isLoading; - private IStyle? _loaded; - private Styles _sharedStyles = new(); - private Styles _simpleDark = new(); - private Styles _simpleLight = new(); - /// - /// Initializes a new instance of the class. - /// - /// The base URL for the XAML context. - public SimpleTheme(Uri? baseUri = null) - { - _baseUri = baseUri ?? new Uri("avares://Avalonia.Themes.Simple/"); - InitStyles(_baseUri); - } - - /// - /// Initializes a new instance of the class. - /// - /// The XAML service provider. - public SimpleTheme(IServiceProvider serviceProvider) - { - var service = serviceProvider.GetService(typeof(IUriContext)); - if (service == null) - { - throw new Exception("There is no service object of type IUriContext!"); - } - _baseUri = ((IUriContext)service).BaseUri; - InitStyles(_baseUri); - } - - public event EventHandler? OwnerChanged - { - add - { - if (Loaded is IResourceProvider rp) - { - rp.OwnerChanged += value; - } - } - remove - { - if (Loaded is IResourceProvider rp) - { - rp.OwnerChanged -= value; - } - } - } - - IReadOnlyList IStyle.Children => _loaded?.Children ?? Array.Empty(); - - bool IResourceNode.HasResources => (Loaded as IResourceProvider)?.HasResources ?? false; - - public IStyle Loaded - { - get - { - if (_loaded == null) - { - _isLoading = true; - - if (Mode == SimpleThemeMode.Light) - { - _loaded = new Styles { _sharedStyles, _simpleLight }; - } - else if (Mode == SimpleThemeMode.Dark) - { - _loaded = new Styles { _sharedStyles, _simpleDark }; - } - _isLoading = false; - } - - return _loaded!; - } - } - - /// - /// Gets or sets the mode of the fluent theme (light, dark). - /// - public SimpleThemeMode Mode - { - get => GetValue(ModeProperty); - set => SetValue(ModeProperty, value); - } - public IResourceHost? Owner => (Loaded as IResourceProvider)?.Owner; - - void IResourceProvider.AddOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.AddOwner(owner); - - void IResourceProvider.RemoveOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.RemoveOwner(owner); - - public SelectorMatchResult TryAttach(IStyleable target, object? host) => Loaded.TryAttach(target, host); - - public bool TryGetResource(object key, out object? value) - { - if (!_isLoading && Loaded is IResourceProvider p) - { - return p.TryGetResource(key, out value); - } - - value = null; - return false; - } - - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) - { - base.OnPropertyChanged(change); - if (change.Property == ModeProperty) - { - if (Mode == SimpleThemeMode.Dark) - { - (Loaded as Styles)![1] = _simpleDark[0]; - } - else - { - (Loaded as Styles)![1] = _simpleLight[0]; - } - } - } - - private void InitStyles(Uri baseUri) - { - _sharedStyles = new Styles - { - new StyleInclude(baseUri) - { - Source = new Uri("avares://Avalonia.Themes.Simple/Controls/SimpleControls.xaml") - }, - new StyleInclude(baseUri) - { - Source = new Uri("avares://Avalonia.Themes.Simple/Accents/Base.xaml") - } - }; - _simpleLight = new Styles - { - new StyleInclude(baseUri) - { - Source = new Uri("avares://Avalonia.Themes.Simple/Accents/BaseLight.xaml") - } - }; - - _simpleDark = new Styles - { - new StyleInclude(baseUri) - { - Source = new Uri("avares://Avalonia.Themes.Simple/Accents/BaseDark.xaml") - } - }; - } - - } -} diff --git a/src/Avalonia.Themes.Simple/SimpleTheme.xaml b/src/Avalonia.Themes.Simple/SimpleTheme.xaml new file mode 100644 index 0000000000..c8bf748595 --- /dev/null +++ b/src/Avalonia.Themes.Simple/SimpleTheme.xaml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs b/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs new file mode 100644 index 0000000000..af9d305043 --- /dev/null +++ b/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; + +namespace Avalonia.Themes.Simple +{ + public class SimpleTheme : Styles + { + public static readonly StyledProperty ModeProperty = + AvaloniaProperty.Register(nameof(Mode)); + + private readonly IResourceDictionary _simpleDark; + private readonly IResourceDictionary _simpleLight; + + /// + /// Initializes a new instance of the class. + /// + public SimpleTheme() + { + AvaloniaXamlLoader.Load(this); + + _simpleDark = (IResourceDictionary)GetAndRemove("BaseDark"); + _simpleLight = (IResourceDictionary)GetAndRemove("BaseLight"); + EnsureThemeVariant(); + + object GetAndRemove(string key) + { + var val = Resources[key] + ?? throw new KeyNotFoundException($"Key {key} was not found in the resources"); + Resources.Remove(key); + return val; + } + } + + /// + /// Gets or sets the mode of the fluent theme (light, dark). + /// + public SimpleThemeMode Mode + { + get => GetValue(ModeProperty); + set => SetValue(ModeProperty, value); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ModeProperty) + { + EnsureThemeVariant(); + } + } + + private void EnsureThemeVariant() + { + var themeVariantResource = Mode == SimpleThemeMode.Dark ? _simpleDark : _simpleLight; + var dict = Resources.MergedDictionaries; + if (dict.Count == 1) + { + dict.Add(themeVariantResource); + } + else + { + dict[1] = themeVariantResource; + } + } + } +} diff --git a/tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs b/tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs index ceb015ee63..ae874b8a61 100644 --- a/tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs @@ -61,7 +61,7 @@ namespace Avalonia.Benchmarks.Themes AssetLoader.RegisterResUriParsers(); return new Styles { - new Avalonia.Themes.Fluent.FluentTheme(new Uri("avares://Avalonia.Benchmarks")) + new Avalonia.Themes.Fluent.FluentTheme() { } diff --git a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs index 86ba4c6005..e82576c7d9 100644 --- a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs @@ -31,7 +31,7 @@ namespace Avalonia.Benchmarks.Themes [Arguments(FluentThemeMode.Light)] public bool InitFluentTheme(FluentThemeMode mode) { - UnitTestApplication.Current.Styles[0] = new FluentTheme(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) + UnitTestApplication.Current.Styles[0] = new FluentTheme() { Mode = mode }; @@ -43,7 +43,7 @@ namespace Avalonia.Benchmarks.Themes [Arguments(SimpleThemeMode.Light)] public bool InitSimpleTheme(SimpleThemeMode mode) { - UnitTestApplication.Current.Styles[0] = new SimpleTheme(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) + UnitTestApplication.Current.Styles[0] = new SimpleTheme() { Mode = mode }; From 4043c6aa665c38d0ee4b6564b9ec5743ee4d4c8e Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 16 Nov 2022 21:44:32 -0500 Subject: [PATCH 111/136] Minor formatting issues --- .../XamlCompilerTaskExecutor.cs | 40 +++++++++---------- src/Avalonia.Themes.Fluent/FluentTheme.xaml | 2 +- src/Avalonia.Themes.Simple/SimpleTheme.xaml | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index e68cb0013a..75abfe352f 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -57,40 +57,40 @@ namespace Avalonia.Build.Tasks { try { - references = references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")).ToArray(); - var typeSystem = new CecilTypeSystem(references, input); - var refTypeSystem = !string.IsNullOrWhiteSpace(refInput) && File.Exists(refInput) ? new CecilTypeSystem(references, refInput) : null; - - var asm = typeSystem.TargetAssemblyDefinition; - var refAsm = refTypeSystem?.TargetAssemblyDefinition; - if (!skipXamlCompilation) - { + references = references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")).ToArray(); + var typeSystem = new CecilTypeSystem(references, input); + var refTypeSystem = !string.IsNullOrWhiteSpace(refInput) && File.Exists(refInput) ? new CecilTypeSystem(references, refInput) : null; + + var asm = typeSystem.TargetAssemblyDefinition; + var refAsm = refTypeSystem?.TargetAssemblyDefinition; + if (!skipXamlCompilation) + { var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, defaultCompileBindings, logImportance, debuggerLaunch); if (compileRes == null) return new CompileResult(true); if (compileRes == false) return new CompileResult(false); - + if (refTypeSystem is not null) { var refCompileRes = CompileCoreForRefAssembly(engine, typeSystem, refTypeSystem); if (refCompileRes == false) return new CompileResult(false); } - } + } - var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols }; - if (!string.IsNullOrWhiteSpace(strongNameKey)) + var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols }; + if (!string.IsNullOrWhiteSpace(strongNameKey)) writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey); - - asm.Write(output, writerParameters); - - var refWriterParameters = new WriterParameters { WriteSymbols = false }; - if (!string.IsNullOrWhiteSpace(strongNameKey)) + + asm.Write(output, writerParameters); + + var refWriterParameters = new WriterParameters { WriteSymbols = false }; + if (!string.IsNullOrWhiteSpace(strongNameKey)) writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey); - refAsm?.Write(refOutput, refWriterParameters); - - return new CompileResult(true, true); + refAsm?.Write(refOutput, refWriterParameters); + + return new CompileResult(true, true); } catch (Exception ex) { diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml index 1d710bb9dc..d8f8267fe5 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml @@ -8,7 +8,7 @@ - + diff --git a/src/Avalonia.Themes.Simple/SimpleTheme.xaml b/src/Avalonia.Themes.Simple/SimpleTheme.xaml index c8bf748595..fe296bd288 100644 --- a/src/Avalonia.Themes.Simple/SimpleTheme.xaml +++ b/src/Avalonia.Themes.Simple/SimpleTheme.xaml @@ -7,7 +7,7 @@ - + From c29fff034205590be775fe46e9bfdd02824b94c5 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 17 Nov 2022 10:16:35 +0000 Subject: [PATCH 112/136] rename avalonia.web to avalonia.browser --- .gitignore | 10 +- Avalonia.sln | 54 +- .../App.razor | 0 .../App.razor.cs | 4 +- .../ControlCatalog.Browser.Blazor.csproj} | 6 +- .../Pages/Index.razor | 2 +- .../Program.cs | 2 +- .../Properties/launchSettings.json | 0 .../Shared/MainLayout.razor | 0 .../_Imports.razor | 2 +- .../wwwroot/css/app.css | 0 .../wwwroot/favicon.ico | Bin .../wwwroot/index.html | 0 .../ControlCatalog.Browser.csproj} | 6 +- .../EmbedSample.Browser.cs | 4 +- .../Logo.svg | 0 .../Program.cs | 4 +- .../Roots.xml | 0 .../app.css | 0 .../embed.js | 0 .../favicon.ico | Bin .../index.html | 0 .../main.js | 0 .../runtimeconfig.template.json | 0 src/Avalonia.Base/Avalonia.Base.csproj | 1 - src/Avalonia.Base/Properties/AssemblyInfo.cs | 4 +- .../Avalonia.Browser.Blazor.csproj} | 8 +- .../Avalonia.Browser.Blazor}/AvaloniaView.cs | 11 +- .../BlazorSingleViewLifetime.cs | 4 +- .../Avalonia.Browser/Avalonia.Browser.csproj} | 2 +- .../Avalonia.Browser/Avalonia.Browser.props} | 0 .../Avalonia.Browser.targets} | 0 .../Avalonia.Browser}/AvaloniaView.cs | 8 +- .../BrowserNativeControlHost.cs | 5 +- .../BrowserRuntimePlatform.cs | 4 +- .../BrowserSingleViewLifetime.cs | 4 +- .../Avalonia.Browser}/BrowserTopLevelImpl.cs | 6 +- .../Avalonia.Browser}/ClipboardImpl.cs | 4 +- .../Avalonia.Browser}/Cursor.cs | 2 +- .../Interop/AvaloniaModule.cs | 2 +- .../Avalonia.Browser}/Interop/CanvasHelper.cs | 2 +- .../Avalonia.Browser}/Interop/DomHelper.cs | 2 +- .../Avalonia.Browser}/Interop/InputHelper.cs | 2 +- .../Interop/NativeControlHostHelper.cs | 2 +- .../Interop/StorageHelper.cs | 2 +- .../Avalonia.Browser}/Interop/StreamHelper.cs | 2 +- .../JSObjectControlHandle.cs | 2 +- .../Avalonia.Browser}/Keycodes.cs | 2 +- .../ManualTriggerRenderTimer.cs | 2 +- .../Avalonia.Browser}/Skia/BrowserSkiaGpu.cs | 2 +- .../Skia/BrowserSkiaGpuRenderSession.cs | 2 +- .../Skia/BrowserSkiaGpuRenderTarget.cs | 2 +- .../Skia/BrowserSkiaRasterSurface.cs | 2 +- .../Skia/BrowserSkiaSurface.cs | 4 +- .../Skia/IBrowserSkiaSurface.cs | 2 +- .../Storage/BlobReadableStream.cs | 5 +- .../Storage/BrowserStorageProvider.cs | 5 +- .../Storage/WriteableStream.cs | 5 +- .../WebEmbeddableControlRoot.cs | 2 +- .../Avalonia.Browser}/WinStubs.cs | 2 +- .../Avalonia.Browser}/WindowingPlatform.cs | 2 +- .../Avalonia.Browser}/interop.js | 0 .../Avalonia.Browser}/webapp/.eslintrc.json | 0 .../Avalonia.Browser}/webapp/build.js | 0 .../webapp/modules/avalonia.ts | 0 .../webapp/modules/avalonia/caniuse.ts | 0 .../webapp/modules/avalonia/canvas.ts | 0 .../webapp/modules/avalonia/caretHelper.ts | 0 .../webapp/modules/avalonia/dom.ts | 0 .../webapp/modules/avalonia/input.ts | 0 .../modules/avalonia/nativeControlHost.ts | 0 .../webapp/modules/avalonia/stream.ts | 0 .../webapp/modules/storage.ts | 0 .../webapp/modules/storage/indexedDb.ts | 0 .../webapp/modules/storage/storageItem.ts | 0 .../webapp/modules/storage/storageProvider.ts | 0 .../Avalonia.Browser/webapp/package-lock.json | 5496 +++++++++++++++++ .../Avalonia.Browser}/webapp/package.json | 2 +- .../Avalonia.Browser}/webapp/tsconfig.json | 0 .../webapp/types/dotnet.d.ts | 0 src/Web/Avalonia.Web/webapp/package-lock.json | 2234 ------- 81 files changed, 5600 insertions(+), 2344 deletions(-) rename samples/{ControlCatalog.Blazor.Web => ControlCatalog.Browser.Blazor}/App.razor (100%) rename samples/{ControlCatalog.Blazor.Web => ControlCatalog.Browser.Blazor}/App.razor.cs (83%) rename samples/{ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj => ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj} (79%) rename samples/{ControlCatalog.Blazor.Web => ControlCatalog.Browser.Blazor}/Pages/Index.razor (50%) rename samples/{ControlCatalog.Blazor.Web => ControlCatalog.Browser.Blazor}/Program.cs (94%) rename samples/{ControlCatalog.Blazor.Web => ControlCatalog.Browser.Blazor}/Properties/launchSettings.json (100%) rename samples/{ControlCatalog.Blazor.Web => ControlCatalog.Browser.Blazor}/Shared/MainLayout.razor (100%) rename samples/{ControlCatalog.Blazor.Web => ControlCatalog.Browser.Blazor}/_Imports.razor (88%) rename samples/{ControlCatalog.Blazor.Web => ControlCatalog.Browser.Blazor}/wwwroot/css/app.css (100%) rename samples/{ControlCatalog.Blazor.Web => ControlCatalog.Browser.Blazor}/wwwroot/favicon.ico (100%) rename samples/{ControlCatalog.Blazor.Web => ControlCatalog.Browser.Blazor}/wwwroot/index.html (100%) rename samples/{ControlCatalog.Web/ControlCatalog.Web.csproj => ControlCatalog.Browser/ControlCatalog.Browser.csproj} (85%) rename samples/{ControlCatalog.Web => ControlCatalog.Browser}/EmbedSample.Browser.cs (95%) rename samples/{ControlCatalog.Web => ControlCatalog.Browser}/Logo.svg (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Browser}/Program.cs (90%) rename samples/{ControlCatalog.Web => ControlCatalog.Browser}/Roots.xml (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Browser}/app.css (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Browser}/embed.js (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Browser}/favicon.ico (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Browser}/index.html (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Browser}/main.js (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Browser}/runtimeconfig.template.json (100%) rename src/{Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj => Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj} (87%) rename src/{Web/Avalonia.Web.Blazor => Browser/Avalonia.Browser.Blazor}/AvaloniaView.cs (79%) rename src/{Web/Avalonia.Web.Blazor => Browser/Avalonia.Browser.Blazor}/BlazorSingleViewLifetime.cs (92%) rename src/{Web/Avalonia.Web/Avalonia.Web.csproj => Browser/Avalonia.Browser/Avalonia.Browser.csproj} (95%) rename src/{Web/Avalonia.Web/Avalonia.Web.props => Browser/Avalonia.Browser/Avalonia.Browser.props} (100%) rename src/{Web/Avalonia.Web/Avalonia.Web.targets => Browser/Avalonia.Browser/Avalonia.Browser.targets} (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/AvaloniaView.cs (99%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/BrowserNativeControlHost.cs (98%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/BrowserRuntimePlatform.cs (92%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/BrowserSingleViewLifetime.cs (95%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/BrowserTopLevelImpl.cs (98%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/ClipboardImpl.cs (92%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Cursor.cs (99%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Interop/AvaloniaModule.cs (96%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Interop/CanvasHelper.cs (97%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Interop/DomHelper.cs (96%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Interop/InputHelper.cs (99%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Interop/NativeControlHostHelper.cs (97%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Interop/StorageHelper.cs (98%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Interop/StreamHelper.cs (97%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/JSObjectControlHandle.cs (96%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Keycodes.cs (99%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/ManualTriggerRenderTimer.cs (94%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Skia/BrowserSkiaGpu.cs (95%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Skia/BrowserSkiaGpuRenderSession.cs (96%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Skia/BrowserSkiaGpuRenderTarget.cs (97%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Skia/BrowserSkiaRasterSurface.cs (98%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Skia/BrowserSkiaSurface.cs (91%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Skia/IBrowserSkiaSurface.cs (82%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Storage/BlobReadableStream.cs (97%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Storage/BrowserStorageProvider.cs (99%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/Storage/WriteableStream.cs (97%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/WebEmbeddableControlRoot.cs (98%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/WinStubs.cs (97%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/WindowingPlatform.cs (99%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/interop.js (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/.eslintrc.json (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/build.js (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/modules/avalonia.ts (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/modules/avalonia/caniuse.ts (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/modules/avalonia/canvas.ts (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/modules/avalonia/caretHelper.ts (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/modules/avalonia/dom.ts (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/modules/avalonia/input.ts (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/modules/avalonia/nativeControlHost.ts (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/modules/avalonia/stream.ts (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/modules/storage.ts (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/modules/storage/indexedDb.ts (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/modules/storage/storageItem.ts (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/modules/storage/storageProvider.ts (100%) create mode 100644 src/Browser/Avalonia.Browser/webapp/package-lock.json rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/package.json (95%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/tsconfig.json (100%) rename src/{Web/Avalonia.Web => Browser/Avalonia.Browser}/webapp/types/dotnet.d.ts (100%) delete mode 100644 src/Web/Avalonia.Web/webapp/package-lock.json diff --git a/.gitignore b/.gitignore index 61a3b53de1..bbf358b8f4 100644 --- a/.gitignore +++ b/.gitignore @@ -210,9 +210,9 @@ coc-settings.json .ccls-cache .ccls *.map -src/Web/Avalonia.Web.Blazor/wwwroot/*.js -src/Web/Avalonia.Web.Blazor/Interop/Typescript/*.js +src/Browser/Avalonia.Browser.Blazor/wwwroot/*.js +src/Browser/Avalonia.Browser.Blazor/Interop/Typescript/*.js node_modules -src/Web/Avalonia.Web.Blazor/webapp/package-lock.json -src/Web/Avalonia.Web.Blazor/wwwroot -src/Web/Avalonia.Web/wwwroot +src/Browser/Avalonia.Browser.Blazor/webapp/package-lock.json +src/Browser/Avalonia.Browser.Blazor/wwwroot +src/Browser/Avalonia.Browser/wwwroot diff --git a/Avalonia.sln b/Avalonia.sln index e2f04ddc35..34b5596119 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -198,9 +198,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTestApp", "sampl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.IntegrationTests.Appium", "tests\Avalonia.IntegrationTests.Appium\Avalonia.IntegrationTests.Appium.csproj", "{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Blazor", "src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj", "{25831348-EB2A-483E-9576-E8F6528674A5}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Browser", "Browser", "{86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "samples\interop\WindowsInteropTest\WindowsInteropTest.csproj", "{26A98DA1-D89D-4A95-8152-349F404DA2E2}" EndProject @@ -216,8 +214,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport.Te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevGenerators", "src\tools\DevGenerators\DevGenerators.csproj", "{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web", "src\Web\Avalonia.Web\Avalonia.Web.csproj", "{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Android", "samples\MobileSandbox.Android\MobileSandbox.Android.csproj", "{C90FE60B-B01E-4F35-91D6-379D6966030F}" @@ -226,9 +222,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.iOS", "sample EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Blazor.Web", "samples\ControlCatalog.Blazor.Web\ControlCatalog.Blazor.Web.csproj", "{6A710364-AE6D-40BD-968B-024311527AC2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Browser", "src\Browser\Avalonia.Browser\Avalonia.Browser.csproj", "{4A39637C-9338-4925-A4DB-D072E292EC78}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Browser.Blazor", "src\Browser\Avalonia.Browser.Blazor\Avalonia.Browser.Blazor.csproj", "{47F8530C-F19B-4B1A-B4D6-EB231522AE5D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Browser", "samples\ControlCatalog.Browser\ControlCatalog.Browser.csproj", "{15B93A4C-1B46-43F6-B534-7B25B6E99932}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Web", "samples\ControlCatalog.Web\ControlCatalog.Web.csproj", "{8B3E8405-DE18-4048-A459-9CA4AC3319A2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Browser.Blazor", "samples\ControlCatalog.Browser.Blazor\ControlCatalog.Browser.Blazor.csproj", "{90B08091-9BBD-4362-B712-E9F2CC62B218}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -480,10 +480,6 @@ Global {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Debug|Any CPU.Build.0 = Debug|Any CPU {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Release|Any CPU.ActiveCfg = Release|Any CPU {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Release|Any CPU.Build.0 = Release|Any CPU - {25831348-EB2A-483E-9576-E8F6528674A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {25831348-EB2A-483E-9576-E8F6528674A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.Build.0 = Release|Any CPU {26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {26A98DA1-D89D-4A95-8152-349F404DA2E2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -512,10 +508,6 @@ Global {1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}.Debug|Any CPU.Build.0 = Debug|Any CPU {1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}.Release|Any CPU.ActiveCfg = Release|Any CPU {1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}.Release|Any CPU.Build.0 = Release|Any CPU - {76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.Build.0 = Release|Any CPU {3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.Build.0 = Debug|Any CPU {3B8519C1-2F51-4F12-A348-120AB91D4532}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -533,14 +525,22 @@ Global {62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Debug|Any CPU.Build.0 = Debug|Any CPU {62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.ActiveCfg = Release|Any CPU {62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.Build.0 = Release|Any CPU - {6A710364-AE6D-40BD-968B-024311527AC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6A710364-AE6D-40BD-968B-024311527AC2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6A710364-AE6D-40BD-968B-024311527AC2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6A710364-AE6D-40BD-968B-024311527AC2}.Release|Any CPU.Build.0 = Release|Any CPU - {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Release|Any CPU.Build.0 = Release|Any CPU + {4A39637C-9338-4925-A4DB-D072E292EC78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A39637C-9338-4925-A4DB-D072E292EC78}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A39637C-9338-4925-A4DB-D072E292EC78}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A39637C-9338-4925-A4DB-D072E292EC78}.Release|Any CPU.Build.0 = Release|Any CPU + {47F8530C-F19B-4B1A-B4D6-EB231522AE5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47F8530C-F19B-4B1A-B4D6-EB231522AE5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47F8530C-F19B-4B1A-B4D6-EB231522AE5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47F8530C-F19B-4B1A-B4D6-EB231522AE5D}.Release|Any CPU.Build.0 = Release|Any CPU + {15B93A4C-1B46-43F6-B534-7B25B6E99932}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {15B93A4C-1B46-43F6-B534-7B25B6E99932}.Debug|Any CPU.Build.0 = Debug|Any CPU + {15B93A4C-1B46-43F6-B534-7B25B6E99932}.Release|Any CPU.ActiveCfg = Release|Any CPU + {15B93A4C-1B46-43F6-B534-7B25B6E99932}.Release|Any CPU.Build.0 = Release|Any CPU + {90B08091-9BBD-4362-B712-E9F2CC62B218}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90B08091-9BBD-4362-B712-E9F2CC62B218}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90B08091-9BBD-4362-B712-E9F2CC62B218}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90B08091-9BBD-4362-B712-E9F2CC62B218}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -591,20 +591,20 @@ Global {BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098} {676D6BFD-029D-4E43-BFC7-3892265CE251} = {9B9E3891-2366-4253-A952-D08BCEB71098} {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} - {25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} {26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098} {70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {2B390431-288C-435C-BB6B-A374033BD8D1} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {EABE2161-989B-42BF-BD8D-1E34B20C21F1} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} - {76D39FF6-6B4F-46C4-93CD-E6FC4665739E} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} {3B8519C1-2F51-4F12-A348-120AB91D4532} = {9B9E3891-2366-4253-A952-D08BCEB71098} {C90FE60B-B01E-4F35-91D6-379D6966030F} = {9B9E3891-2366-4253-A952-D08BCEB71098} {FED9A71D-00D7-4F40-A9E4-1229EEA28EEB} = {9B9E3891-2366-4253-A952-D08BCEB71098} {62D392C9-81CF-487F-92E8-598B2AF3FDCE} = {9B9E3891-2366-4253-A952-D08BCEB71098} - {6A710364-AE6D-40BD-968B-024311527AC2} = {9B9E3891-2366-4253-A952-D08BCEB71098} - {8B3E8405-DE18-4048-A459-9CA4AC3319A2} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {4A39637C-9338-4925-A4DB-D072E292EC78} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} + {47F8530C-F19B-4B1A-B4D6-EB231522AE5D} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} + {15B93A4C-1B46-43F6-B534-7B25B6E99932} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {90B08091-9BBD-4362-B712-E9F2CC62B218} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/samples/ControlCatalog.Blazor.Web/App.razor b/samples/ControlCatalog.Browser.Blazor/App.razor similarity index 100% rename from samples/ControlCatalog.Blazor.Web/App.razor rename to samples/ControlCatalog.Browser.Blazor/App.razor diff --git a/samples/ControlCatalog.Blazor.Web/App.razor.cs b/samples/ControlCatalog.Browser.Blazor/App.razor.cs similarity index 83% rename from samples/ControlCatalog.Blazor.Web/App.razor.cs rename to samples/ControlCatalog.Browser.Blazor/App.razor.cs index 8cc0095f20..f38db2b055 100644 --- a/samples/ControlCatalog.Blazor.Web/App.razor.cs +++ b/samples/ControlCatalog.Browser.Blazor/App.razor.cs @@ -1,7 +1,7 @@ using Avalonia; -using Avalonia.Web.Blazor; +using Avalonia.Browser.Blazor; -namespace ControlCatalog.Blazor.Web; +namespace ControlCatalog.Browser.Blazor; public partial class App { diff --git a/samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj b/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj similarity index 79% rename from samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj rename to samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj index 03fb31f0d3..d0fb614840 100644 --- a/samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj +++ b/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj @@ -15,15 +15,15 @@ - + - - + + diff --git a/samples/ControlCatalog.Blazor.Web/Pages/Index.razor b/samples/ControlCatalog.Browser.Blazor/Pages/Index.razor similarity index 50% rename from samples/ControlCatalog.Blazor.Web/Pages/Index.razor rename to samples/ControlCatalog.Browser.Blazor/Pages/Index.razor index 93ca07f9f1..7480e4c5e9 100644 --- a/samples/ControlCatalog.Blazor.Web/Pages/Index.razor +++ b/samples/ControlCatalog.Browser.Blazor/Pages/Index.razor @@ -1,5 +1,5 @@ @page "/" -@using Avalonia.Web.Blazor +@using Avalonia.Browser.Blazor diff --git a/samples/ControlCatalog.Blazor.Web/Program.cs b/samples/ControlCatalog.Browser.Blazor/Program.cs similarity index 94% rename from samples/ControlCatalog.Blazor.Web/Program.cs rename to samples/ControlCatalog.Browser.Blazor/Program.cs index d71b125fa1..eb99ca518e 100644 --- a/samples/ControlCatalog.Blazor.Web/Program.cs +++ b/samples/ControlCatalog.Browser.Blazor/Program.cs @@ -3,7 +3,7 @@ using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.Extensions.DependencyInjection; -using ControlCatalog.Blazor.Web; +using ControlCatalog.Browser.Blazor; public class Program { diff --git a/samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json b/samples/ControlCatalog.Browser.Blazor/Properties/launchSettings.json similarity index 100% rename from samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json rename to samples/ControlCatalog.Browser.Blazor/Properties/launchSettings.json diff --git a/samples/ControlCatalog.Blazor.Web/Shared/MainLayout.razor b/samples/ControlCatalog.Browser.Blazor/Shared/MainLayout.razor similarity index 100% rename from samples/ControlCatalog.Blazor.Web/Shared/MainLayout.razor rename to samples/ControlCatalog.Browser.Blazor/Shared/MainLayout.razor diff --git a/samples/ControlCatalog.Blazor.Web/_Imports.razor b/samples/ControlCatalog.Browser.Blazor/_Imports.razor similarity index 88% rename from samples/ControlCatalog.Blazor.Web/_Imports.razor rename to samples/ControlCatalog.Browser.Blazor/_Imports.razor index 0e6d11b419..dc4f778352 100644 --- a/samples/ControlCatalog.Blazor.Web/_Imports.razor +++ b/samples/ControlCatalog.Browser.Blazor/_Imports.razor @@ -6,5 +6,5 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.JSInterop -@using ControlCatalog.Blazor.Web.Shared +@using ControlCatalog.Browser.Blazor.Shared @using SkiaSharp diff --git a/samples/ControlCatalog.Blazor.Web/wwwroot/css/app.css b/samples/ControlCatalog.Browser.Blazor/wwwroot/css/app.css similarity index 100% rename from samples/ControlCatalog.Blazor.Web/wwwroot/css/app.css rename to samples/ControlCatalog.Browser.Blazor/wwwroot/css/app.css diff --git a/samples/ControlCatalog.Blazor.Web/wwwroot/favicon.ico b/samples/ControlCatalog.Browser.Blazor/wwwroot/favicon.ico similarity index 100% rename from samples/ControlCatalog.Blazor.Web/wwwroot/favicon.ico rename to samples/ControlCatalog.Browser.Blazor/wwwroot/favicon.ico diff --git a/samples/ControlCatalog.Blazor.Web/wwwroot/index.html b/samples/ControlCatalog.Browser.Blazor/wwwroot/index.html similarity index 100% rename from samples/ControlCatalog.Blazor.Web/wwwroot/index.html rename to samples/ControlCatalog.Browser.Blazor/wwwroot/index.html diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Browser/ControlCatalog.Browser.csproj similarity index 85% rename from samples/ControlCatalog.Web/ControlCatalog.Web.csproj rename to samples/ControlCatalog.Browser/ControlCatalog.Browser.csproj index 06a5466619..c4278459f3 100644 --- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj +++ b/samples/ControlCatalog.Browser/ControlCatalog.Browser.csproj @@ -26,7 +26,7 @@ - + @@ -39,6 +39,6 @@ - - + + diff --git a/samples/ControlCatalog.Web/EmbedSample.Browser.cs b/samples/ControlCatalog.Browser/EmbedSample.Browser.cs similarity index 95% rename from samples/ControlCatalog.Web/EmbedSample.Browser.cs rename to samples/ControlCatalog.Browser/EmbedSample.Browser.cs index 5cfbb608cc..c367230ddf 100644 --- a/samples/ControlCatalog.Web/EmbedSample.Browser.cs +++ b/samples/ControlCatalog.Browser/EmbedSample.Browser.cs @@ -1,11 +1,11 @@ using System; using System.Runtime.InteropServices.JavaScript; using Avalonia.Platform; -using Avalonia.Web; +using Avalonia.Browser; using ControlCatalog.Pages; -namespace ControlCatalog.Web; +namespace ControlCatalog.Browser; public class EmbedSampleWeb : INativeDemoControl { diff --git a/samples/ControlCatalog.Web/Logo.svg b/samples/ControlCatalog.Browser/Logo.svg similarity index 100% rename from samples/ControlCatalog.Web/Logo.svg rename to samples/ControlCatalog.Browser/Logo.svg diff --git a/samples/ControlCatalog.Web/Program.cs b/samples/ControlCatalog.Browser/Program.cs similarity index 90% rename from samples/ControlCatalog.Web/Program.cs rename to samples/ControlCatalog.Browser/Program.cs index 7d05c8e462..53b7c60a6f 100644 --- a/samples/ControlCatalog.Web/Program.cs +++ b/samples/ControlCatalog.Browser/Program.cs @@ -1,8 +1,8 @@ using System.Runtime.Versioning; using Avalonia; -using Avalonia.Web; +using Avalonia.Browser; using ControlCatalog; -using ControlCatalog.Web; +using ControlCatalog.Browser; [assembly:SupportedOSPlatform("browser")] diff --git a/samples/ControlCatalog.Web/Roots.xml b/samples/ControlCatalog.Browser/Roots.xml similarity index 100% rename from samples/ControlCatalog.Web/Roots.xml rename to samples/ControlCatalog.Browser/Roots.xml diff --git a/samples/ControlCatalog.Web/app.css b/samples/ControlCatalog.Browser/app.css similarity index 100% rename from samples/ControlCatalog.Web/app.css rename to samples/ControlCatalog.Browser/app.css diff --git a/samples/ControlCatalog.Web/embed.js b/samples/ControlCatalog.Browser/embed.js similarity index 100% rename from samples/ControlCatalog.Web/embed.js rename to samples/ControlCatalog.Browser/embed.js diff --git a/samples/ControlCatalog.Web/favicon.ico b/samples/ControlCatalog.Browser/favicon.ico similarity index 100% rename from samples/ControlCatalog.Web/favicon.ico rename to samples/ControlCatalog.Browser/favicon.ico diff --git a/samples/ControlCatalog.Web/index.html b/samples/ControlCatalog.Browser/index.html similarity index 100% rename from samples/ControlCatalog.Web/index.html rename to samples/ControlCatalog.Browser/index.html diff --git a/samples/ControlCatalog.Web/main.js b/samples/ControlCatalog.Browser/main.js similarity index 100% rename from samples/ControlCatalog.Web/main.js rename to samples/ControlCatalog.Browser/main.js diff --git a/samples/ControlCatalog.Web/runtimeconfig.template.json b/samples/ControlCatalog.Browser/runtimeconfig.template.json similarity index 100% rename from samples/ControlCatalog.Web/runtimeconfig.template.json rename to samples/ControlCatalog.Browser/runtimeconfig.template.json diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index d8fcce803f..feec7f1a74 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -40,7 +40,6 @@ - diff --git a/src/Avalonia.Base/Properties/AssemblyInfo.cs b/src/Avalonia.Base/Properties/AssemblyInfo.cs index d5e01087ef..260771ea9d 100644 --- a/src/Avalonia.Base/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Base/Properties/AssemblyInfo.cs @@ -29,6 +29,6 @@ using Avalonia.Metadata; [assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: InternalsVisibleTo("Avalonia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] -[assembly: InternalsVisibleTo("Avalonia.Web.Blazor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] -[assembly: InternalsVisibleTo("Avalonia.Web, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] +[assembly: InternalsVisibleTo("Avalonia.Browser.Blazor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] +[assembly: InternalsVisibleTo("Avalonia.Browser, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj similarity index 87% rename from src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj rename to src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj index 1c31e0eb5d..4537f8d73e 100644 --- a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj +++ b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj @@ -2,7 +2,7 @@ net7.0 - Avalonia.Web.Blazor + Avalonia.Browser.Blazor _IncludeGeneratedAvaloniaStaticFiles;$(ResolveStaticWebAssetsInputsDependsOn) @@ -17,12 +17,12 @@ - + - <_AvaloniaWebAssets Include="$(MSBuildThisFileDirectory)../Avalonia.Web/wwwroot/**/*.*" /> + <_AvaloniaWebAssets Include="$(MSBuildThisFileDirectory)../Avalonia.Browser/wwwroot/**/*.*" /> diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs b/src/Browser/Avalonia.Browser.Blazor/AvaloniaView.cs similarity index 79% rename from src/Web/Avalonia.Web.Blazor/AvaloniaView.cs rename to src/Browser/Avalonia.Browser.Blazor/AvaloniaView.cs index 909e2dd441..2cc74273c0 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs +++ b/src/Browser/Avalonia.Browser.Blazor/AvaloniaView.cs @@ -2,17 +2,18 @@ using System.Runtime.InteropServices.JavaScript; using System.Runtime.Versioning; using System.Threading.Tasks; using System; +using Avalonia.Browser.Interop; using Avalonia.Controls.ApplicationLifetimes; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; -using BrowserView = Avalonia.Web.AvaloniaView; +using BrowserView = Avalonia.Browser.AvaloniaView; -namespace Avalonia.Web.Blazor; +namespace Avalonia.Browser.Blazor; [SupportedOSPlatform("browser")] public class AvaloniaView : ComponentBase { - private BrowserView? _browserView; + private Browser.AvaloniaView? _browserView; private readonly string _containerId; public AvaloniaView() @@ -32,9 +33,9 @@ public class AvaloniaView : ComponentBase { if (OperatingSystem.IsBrowser()) { - await Avalonia.Web.Interop.AvaloniaModule.ImportMain(); + await AvaloniaModule.ImportMain(); - _browserView = new BrowserView(_containerId); + _browserView = new Browser.AvaloniaView(_containerId); if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime lifetime) { _browserView.Content = lifetime.MainView; diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs b/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs similarity index 92% rename from src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs rename to src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs index f38779f834..2432dc29a3 100644 --- a/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs +++ b/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs @@ -3,7 +3,7 @@ using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; -namespace Avalonia.Web.Blazor; +namespace Avalonia.Browser.Blazor; [SupportedOSPlatform("browser")] public static class WebAppBuilder @@ -21,7 +21,7 @@ public static class WebAppBuilder .UseBrowser() .With(new BrowserPlatformOptions { - FrameworkAssetPathResolver = new(filePath => $"/_content/Avalonia.Web.Blazor/{filePath}") + FrameworkAssetPathResolver = new(filePath => $"/_content/Avalonia.Browser.Blazor/{filePath}") }); } diff --git a/src/Web/Avalonia.Web/Avalonia.Web.csproj b/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj similarity index 95% rename from src/Web/Avalonia.Web/Avalonia.Web.csproj rename to src/Browser/Avalonia.Browser/Avalonia.Browser.csproj index 88b23cdad2..2564140a03 100644 --- a/src/Web/Avalonia.Web/Avalonia.Web.csproj +++ b/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj @@ -50,7 +50,7 @@ - + diff --git a/src/Web/Avalonia.Web/Avalonia.Web.props b/src/Browser/Avalonia.Browser/Avalonia.Browser.props similarity index 100% rename from src/Web/Avalonia.Web/Avalonia.Web.props rename to src/Browser/Avalonia.Browser/Avalonia.Browser.props diff --git a/src/Web/Avalonia.Web/Avalonia.Web.targets b/src/Browser/Avalonia.Browser/Avalonia.Browser.targets similarity index 100% rename from src/Web/Avalonia.Web/Avalonia.Web.targets rename to src/Browser/Avalonia.Browser/Avalonia.Browser.targets diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Browser/Avalonia.Browser/AvaloniaView.cs similarity index 99% rename from src/Web/Avalonia.Web/AvaloniaView.cs rename to src/Browser/Avalonia.Browser/AvaloniaView.cs index 37614399ee..a407e1e4d8 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Browser/Avalonia.Browser/AvaloniaView.cs @@ -2,7 +2,8 @@ using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices.JavaScript; - +using Avalonia.Browser.Interop; +using Avalonia.Browser.Skia; using Avalonia.Collections.Pooled; using Avalonia.Controls; using Avalonia.Controls.Embedding; @@ -12,12 +13,9 @@ using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Rendering.Composition; using Avalonia.Threading; -using Avalonia.Web.Interop; -using Avalonia.Web.Skia; - using SkiaSharp; -namespace Avalonia.Web +namespace Avalonia.Browser { [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings public partial class AvaloniaView : ITextInputMethodImpl diff --git a/src/Web/Avalonia.Web/BrowserNativeControlHost.cs b/src/Browser/Avalonia.Browser/BrowserNativeControlHost.cs similarity index 98% rename from src/Web/Avalonia.Web/BrowserNativeControlHost.cs rename to src/Browser/Avalonia.Browser/BrowserNativeControlHost.cs index 4cdcf627e6..7e91d29019 100644 --- a/src/Web/Avalonia.Web/BrowserNativeControlHost.cs +++ b/src/Browser/Avalonia.Browser/BrowserNativeControlHost.cs @@ -1,12 +1,11 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices.JavaScript; - +using Avalonia.Browser.Interop; using Avalonia.Controls.Platform; using Avalonia.Platform; -using Avalonia.Web.Interop; -namespace Avalonia.Web +namespace Avalonia.Browser { internal class BrowserNativeControlHost : INativeControlHostImpl { diff --git a/src/Web/Avalonia.Web/BrowserRuntimePlatform.cs b/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs similarity index 92% rename from src/Web/Avalonia.Web/BrowserRuntimePlatform.cs rename to src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs index ebcd3a9921..0abc7703da 100644 --- a/src/Web/Avalonia.Web/BrowserRuntimePlatform.cs +++ b/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs @@ -2,10 +2,10 @@ using System; using System.Runtime.InteropServices; using System.Runtime.InteropServices.JavaScript; using System.Text.RegularExpressions; +using Avalonia.Browser.Interop; using Avalonia.Platform; -using Avalonia.Web.Interop; -namespace Avalonia.Web; +namespace Avalonia.Browser; internal class BrowserRuntimePlatform : StandardRuntimePlatform { diff --git a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs similarity index 95% rename from src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs rename to src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs index 0dcc474f76..ee4f6eca9b 100644 --- a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs +++ b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs @@ -1,11 +1,11 @@ using System; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Web.Skia; using System.Runtime.Versioning; +using Avalonia.Browser.Skia; using Avalonia.Platform; -namespace Avalonia.Web; +namespace Avalonia.Browser; [SupportedOSPlatform("browser")] public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime diff --git a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs similarity index 98% rename from src/Web/Avalonia.Web/BrowserTopLevelImpl.cs rename to src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index ed8f417870..69e2d27181 100644 --- a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Avalonia.Browser.Skia; +using Avalonia.Browser.Storage; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; @@ -11,10 +13,8 @@ using Avalonia.Platform; using Avalonia.Platform.Storage; using Avalonia.Rendering; using Avalonia.Rendering.Composition; -using Avalonia.Web.Skia; -using Avalonia.Web.Storage; -namespace Avalonia.Web +namespace Avalonia.Browser { [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings internal class BrowserTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider diff --git a/src/Web/Avalonia.Web/ClipboardImpl.cs b/src/Browser/Avalonia.Browser/ClipboardImpl.cs similarity index 92% rename from src/Web/Avalonia.Web/ClipboardImpl.cs rename to src/Browser/Avalonia.Browser/ClipboardImpl.cs index 793099f55a..f24d607dae 100644 --- a/src/Web/Avalonia.Web/ClipboardImpl.cs +++ b/src/Browser/Avalonia.Browser/ClipboardImpl.cs @@ -1,10 +1,10 @@ using System; using System.Threading.Tasks; +using Avalonia.Browser.Interop; using Avalonia.Input; using Avalonia.Input.Platform; -using Avalonia.Web.Interop; -namespace Avalonia.Web +namespace Avalonia.Browser { internal class ClipboardImpl : IClipboard { diff --git a/src/Web/Avalonia.Web/Cursor.cs b/src/Browser/Avalonia.Browser/Cursor.cs similarity index 99% rename from src/Web/Avalonia.Web/Cursor.cs rename to src/Browser/Avalonia.Browser/Cursor.cs index af7098f800..ec0dcc51b4 100644 --- a/src/Web/Avalonia.Web/Cursor.cs +++ b/src/Browser/Avalonia.Browser/Cursor.cs @@ -3,7 +3,7 @@ using System.IO; using Avalonia.Input; using Avalonia.Platform; -namespace Avalonia.Web +namespace Avalonia.Browser { internal class CssCursor : ICursorImpl { diff --git a/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs b/src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs similarity index 96% rename from src/Web/Avalonia.Web/Interop/AvaloniaModule.cs rename to src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs index 0e54deb515..b283fbaa56 100644 --- a/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs +++ b/src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices.JavaScript; using System.Threading.Tasks; -namespace Avalonia.Web.Interop; +namespace Avalonia.Browser.Interop; internal static partial class AvaloniaModule { diff --git a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs b/src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs similarity index 97% rename from src/Web/Avalonia.Web/Interop/CanvasHelper.cs rename to src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs index 5bbe503bc1..8321b00658 100644 --- a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs @@ -2,7 +2,7 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.JavaScript; -namespace Avalonia.Web.Interop; +namespace Avalonia.Browser.Interop; internal record GLInfo(int ContextId, uint FboId, int Stencils, int Samples, int Depth); diff --git a/src/Web/Avalonia.Web/Interop/DomHelper.cs b/src/Browser/Avalonia.Browser/Interop/DomHelper.cs similarity index 96% rename from src/Web/Avalonia.Web/Interop/DomHelper.cs rename to src/Browser/Avalonia.Browser/Interop/DomHelper.cs index 80f146a57a..b97a03209b 100644 --- a/src/Web/Avalonia.Web/Interop/DomHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/DomHelper.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.InteropServices.JavaScript; -namespace Avalonia.Web.Interop; +namespace Avalonia.Browser.Interop; internal static partial class DomHelper { diff --git a/src/Web/Avalonia.Web/Interop/InputHelper.cs b/src/Browser/Avalonia.Browser/Interop/InputHelper.cs similarity index 99% rename from src/Web/Avalonia.Web/Interop/InputHelper.cs rename to src/Browser/Avalonia.Browser/Interop/InputHelper.cs index 904fa915a8..7a010dc782 100644 --- a/src/Web/Avalonia.Web/Interop/InputHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/InputHelper.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.JavaScript; using System.Threading.Tasks; -namespace Avalonia.Web.Interop; +namespace Avalonia.Browser.Interop; internal static partial class InputHelper { diff --git a/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs b/src/Browser/Avalonia.Browser/Interop/NativeControlHostHelper.cs similarity index 97% rename from src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs rename to src/Browser/Avalonia.Browser/Interop/NativeControlHostHelper.cs index d3baaa2533..a362be82d8 100644 --- a/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/NativeControlHostHelper.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.InteropServices.JavaScript; -namespace Avalonia.Web.Interop; +namespace Avalonia.Browser.Interop; internal static partial class NativeControlHostHelper { diff --git a/src/Web/Avalonia.Web/Interop/StorageHelper.cs b/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs similarity index 98% rename from src/Web/Avalonia.Web/Interop/StorageHelper.cs rename to src/Browser/Avalonia.Browser/Interop/StorageHelper.cs index 9a6cfb9fc2..c44af810d1 100644 --- a/src/Web/Avalonia.Web/Interop/StorageHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices.JavaScript; using System.Threading.Tasks; -namespace Avalonia.Web.Interop; +namespace Avalonia.Browser.Interop; internal static partial class StorageHelper { diff --git a/src/Web/Avalonia.Web/Interop/StreamHelper.cs b/src/Browser/Avalonia.Browser/Interop/StreamHelper.cs similarity index 97% rename from src/Web/Avalonia.Web/Interop/StreamHelper.cs rename to src/Browser/Avalonia.Browser/Interop/StreamHelper.cs index d9de7bcbd8..46fa671779 100644 --- a/src/Web/Avalonia.Web/Interop/StreamHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/StreamHelper.cs @@ -2,7 +2,7 @@ using System.Runtime.InteropServices.JavaScript; using System.Threading.Tasks; -namespace Avalonia.Web.Interop; +namespace Avalonia.Browser.Interop; /// /// Set of FileSystemWritableFileStream and Blob methods. diff --git a/src/Web/Avalonia.Web/JSObjectControlHandle.cs b/src/Browser/Avalonia.Browser/JSObjectControlHandle.cs similarity index 96% rename from src/Web/Avalonia.Web/JSObjectControlHandle.cs rename to src/Browser/Avalonia.Browser/JSObjectControlHandle.cs index e56ca123eb..b0c8cecca6 100644 --- a/src/Web/Avalonia.Web/JSObjectControlHandle.cs +++ b/src/Browser/Avalonia.Browser/JSObjectControlHandle.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices.JavaScript; using Avalonia.Controls.Platform; -namespace Avalonia.Web; +namespace Avalonia.Browser; public class JSObjectControlHandle : INativeControlHostDestroyableControlHandle { diff --git a/src/Web/Avalonia.Web/Keycodes.cs b/src/Browser/Avalonia.Browser/Keycodes.cs similarity index 99% rename from src/Web/Avalonia.Web/Keycodes.cs rename to src/Browser/Avalonia.Browser/Keycodes.cs index d1185f6e45..5b6e3a329a 100644 --- a/src/Web/Avalonia.Web/Keycodes.cs +++ b/src/Browser/Avalonia.Browser/Keycodes.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Avalonia.Input; -namespace Avalonia.Web +namespace Avalonia.Browser { internal static class Keycodes { diff --git a/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs b/src/Browser/Avalonia.Browser/ManualTriggerRenderTimer.cs similarity index 94% rename from src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs rename to src/Browser/Avalonia.Browser/ManualTriggerRenderTimer.cs index 3309a6dd9f..e9a314e823 100644 --- a/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs +++ b/src/Browser/Avalonia.Browser/ManualTriggerRenderTimer.cs @@ -2,7 +2,7 @@ using System; using System.Diagnostics; using Avalonia.Rendering; -namespace Avalonia.Web +namespace Avalonia.Browser { internal class ManualTriggerRenderTimer : IRenderTimer { diff --git a/src/Web/Avalonia.Web/Skia/BrowserSkiaGpu.cs b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs similarity index 95% rename from src/Web/Avalonia.Web/Skia/BrowserSkiaGpu.cs rename to src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs index f80838232b..a96ead93cb 100644 --- a/src/Web/Avalonia.Web/Skia/BrowserSkiaGpu.cs +++ b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using Avalonia.Skia; -namespace Avalonia.Web.Skia +namespace Avalonia.Browser.Skia { public class BrowserSkiaGpu : ISkiaGpu { diff --git a/src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderSession.cs b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderSession.cs similarity index 96% rename from src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderSession.cs rename to src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderSession.cs index a7f7d9db3d..81b37da7bd 100644 --- a/src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderSession.cs +++ b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderSession.cs @@ -1,7 +1,7 @@ using Avalonia.Skia; using SkiaSharp; -namespace Avalonia.Web.Skia +namespace Avalonia.Browser.Skia { internal class BrowserSkiaGpuRenderSession : ISkiaGpuRenderSession { diff --git a/src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderTarget.cs b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderTarget.cs similarity index 97% rename from src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderTarget.cs rename to src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderTarget.cs index dba9b34166..f69dd3c344 100644 --- a/src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderTarget.cs +++ b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderTarget.cs @@ -1,7 +1,7 @@ using Avalonia.Skia; using SkiaSharp; -namespace Avalonia.Web.Skia +namespace Avalonia.Browser.Skia { internal class BrowserSkiaGpuRenderTarget : ISkiaGpuRenderTarget { diff --git a/src/Web/Avalonia.Web/Skia/BrowserSkiaRasterSurface.cs b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaRasterSurface.cs similarity index 98% rename from src/Web/Avalonia.Web/Skia/BrowserSkiaRasterSurface.cs rename to src/Browser/Avalonia.Browser/Skia/BrowserSkiaRasterSurface.cs index c7005583ac..76b07a6827 100644 --- a/src/Web/Avalonia.Web/Skia/BrowserSkiaRasterSurface.cs +++ b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaRasterSurface.cs @@ -5,7 +5,7 @@ using Avalonia.Platform; using Avalonia.Skia; using SkiaSharp; -namespace Avalonia.Web.Skia +namespace Avalonia.Browser.Skia { internal class BrowserSkiaRasterSurface : IBrowserSkiaSurface, IFramebufferPlatformSurface, IDisposable { diff --git a/src/Web/Avalonia.Web/Skia/BrowserSkiaSurface.cs b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaSurface.cs similarity index 91% rename from src/Web/Avalonia.Web/Skia/BrowserSkiaSurface.cs rename to src/Browser/Avalonia.Browser/Skia/BrowserSkiaSurface.cs index 27a206c0ec..418b34a975 100644 --- a/src/Web/Avalonia.Web/Skia/BrowserSkiaSurface.cs +++ b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaSurface.cs @@ -1,7 +1,7 @@ -using Avalonia.Web.Interop; +using Avalonia.Browser.Interop; using SkiaSharp; -namespace Avalonia.Web.Skia +namespace Avalonia.Browser.Skia { internal class BrowserSkiaSurface : IBrowserSkiaSurface { diff --git a/src/Web/Avalonia.Web/Skia/IBrowserSkiaSurface.cs b/src/Browser/Avalonia.Browser/Skia/IBrowserSkiaSurface.cs similarity index 82% rename from src/Web/Avalonia.Web/Skia/IBrowserSkiaSurface.cs rename to src/Browser/Avalonia.Browser/Skia/IBrowserSkiaSurface.cs index 7301ae45cd..1585d7f283 100644 --- a/src/Web/Avalonia.Web/Skia/IBrowserSkiaSurface.cs +++ b/src/Browser/Avalonia.Browser/Skia/IBrowserSkiaSurface.cs @@ -1,4 +1,4 @@ -namespace Avalonia.Web.Skia +namespace Avalonia.Browser.Skia { internal interface IBrowserSkiaSurface { diff --git a/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs b/src/Browser/Avalonia.Browser/Storage/BlobReadableStream.cs similarity index 97% rename from src/Web/Avalonia.Web/Storage/BlobReadableStream.cs rename to src/Browser/Avalonia.Browser/Storage/BlobReadableStream.cs index 77734ea62f..4fce190346 100644 --- a/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs +++ b/src/Browser/Avalonia.Browser/Storage/BlobReadableStream.cs @@ -3,10 +3,9 @@ using System.IO; using System.Runtime.InteropServices.JavaScript; using System.Threading; using System.Threading.Tasks; +using Avalonia.Browser.Interop; -using Avalonia.Web.Interop; - -namespace Avalonia.Web.Storage; +namespace Avalonia.Browser.Storage; [System.Runtime.Versioning.SupportedOSPlatform("browser")] internal class BlobReadableStream : Stream diff --git a/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs similarity index 99% rename from src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs rename to src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs index 3932b79ad0..28de55092b 100644 --- a/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs +++ b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs @@ -6,11 +6,10 @@ using System.Linq; using System.Runtime.InteropServices.JavaScript; using System.Runtime.Versioning; using System.Threading.Tasks; - +using Avalonia.Browser.Interop; using Avalonia.Platform.Storage; -using Avalonia.Web.Interop; -namespace Avalonia.Web.Storage; +namespace Avalonia.Browser.Storage; internal record FilePickerAcceptType(string Description, IReadOnlyDictionary> Accept); diff --git a/src/Web/Avalonia.Web/Storage/WriteableStream.cs b/src/Browser/Avalonia.Browser/Storage/WriteableStream.cs similarity index 97% rename from src/Web/Avalonia.Web/Storage/WriteableStream.cs rename to src/Browser/Avalonia.Browser/Storage/WriteableStream.cs index 09e438c34e..f29f7420ac 100644 --- a/src/Web/Avalonia.Web/Storage/WriteableStream.cs +++ b/src/Browser/Avalonia.Browser/Storage/WriteableStream.cs @@ -3,10 +3,9 @@ using System.IO; using System.Runtime.InteropServices.JavaScript; using System.Threading; using System.Threading.Tasks; +using Avalonia.Browser.Interop; -using Avalonia.Web.Interop; - -namespace Avalonia.Web.Storage; +namespace Avalonia.Browser.Storage; [System.Runtime.Versioning.SupportedOSPlatform("browser")] // Loose wrapper implementaion of a stream on top of FileAPI FileSystemWritableFileStream diff --git a/src/Web/Avalonia.Web/WebEmbeddableControlRoot.cs b/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs similarity index 98% rename from src/Web/Avalonia.Web/WebEmbeddableControlRoot.cs rename to src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs index 19f36403ad..e389ee98ea 100644 --- a/src/Web/Avalonia.Web/WebEmbeddableControlRoot.cs +++ b/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs @@ -4,7 +4,7 @@ using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; -namespace Avalonia.Web +namespace Avalonia.Browser { internal class WebEmbeddableControlRoot : EmbeddableControlRoot { diff --git a/src/Web/Avalonia.Web/WinStubs.cs b/src/Browser/Avalonia.Browser/WinStubs.cs similarity index 97% rename from src/Web/Avalonia.Web/WinStubs.cs rename to src/Browser/Avalonia.Browser/WinStubs.cs index b0961115fe..dee97a3612 100644 --- a/src/Web/Avalonia.Web/WinStubs.cs +++ b/src/Browser/Avalonia.Browser/WinStubs.cs @@ -4,7 +4,7 @@ using Avalonia.Platform; #nullable enable -namespace Avalonia.Web +namespace Avalonia.Browser { internal class IconLoaderStub : IPlatformIconLoader { diff --git a/src/Web/Avalonia.Web/WindowingPlatform.cs b/src/Browser/Avalonia.Browser/WindowingPlatform.cs similarity index 99% rename from src/Web/Avalonia.Web/WindowingPlatform.cs rename to src/Browser/Avalonia.Browser/WindowingPlatform.cs index 828964afa7..6535e9534c 100644 --- a/src/Web/Avalonia.Web/WindowingPlatform.cs +++ b/src/Browser/Avalonia.Browser/WindowingPlatform.cs @@ -6,7 +6,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; -namespace Avalonia.Web +namespace Avalonia.Browser { internal class BrowserWindowingPlatform : IWindowingPlatform, IPlatformThreadingInterface { diff --git a/src/Web/Avalonia.Web/interop.js b/src/Browser/Avalonia.Browser/interop.js similarity index 100% rename from src/Web/Avalonia.Web/interop.js rename to src/Browser/Avalonia.Browser/interop.js diff --git a/src/Web/Avalonia.Web/webapp/.eslintrc.json b/src/Browser/Avalonia.Browser/webapp/.eslintrc.json similarity index 100% rename from src/Web/Avalonia.Web/webapp/.eslintrc.json rename to src/Browser/Avalonia.Browser/webapp/.eslintrc.json diff --git a/src/Web/Avalonia.Web/webapp/build.js b/src/Browser/Avalonia.Browser/webapp/build.js similarity index 100% rename from src/Web/Avalonia.Web/webapp/build.js rename to src/Browser/Avalonia.Browser/webapp/build.js diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia.ts similarity index 100% rename from src/Web/Avalonia.Web/webapp/modules/avalonia.ts rename to src/Browser/Avalonia.Browser/webapp/modules/avalonia.ts diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/caniuse.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/caniuse.ts similarity index 100% rename from src/Web/Avalonia.Web/webapp/modules/avalonia/caniuse.ts rename to src/Browser/Avalonia.Browser/webapp/modules/avalonia/caniuse.ts diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/canvas.ts similarity index 100% rename from src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts rename to src/Browser/Avalonia.Browser/webapp/modules/avalonia/canvas.ts diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/caretHelper.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/caretHelper.ts similarity index 100% rename from src/Web/Avalonia.Web/webapp/modules/avalonia/caretHelper.ts rename to src/Browser/Avalonia.Browser/webapp/modules/avalonia/caretHelper.ts diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts similarity index 100% rename from src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts rename to src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/input.ts similarity index 100% rename from src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts rename to src/Browser/Avalonia.Browser/webapp/modules/avalonia/input.ts diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/nativeControlHost.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/nativeControlHost.ts similarity index 100% rename from src/Web/Avalonia.Web/webapp/modules/avalonia/nativeControlHost.ts rename to src/Browser/Avalonia.Browser/webapp/modules/avalonia/nativeControlHost.ts diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/stream.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/stream.ts similarity index 100% rename from src/Web/Avalonia.Web/webapp/modules/avalonia/stream.ts rename to src/Browser/Avalonia.Browser/webapp/modules/avalonia/stream.ts diff --git a/src/Web/Avalonia.Web/webapp/modules/storage.ts b/src/Browser/Avalonia.Browser/webapp/modules/storage.ts similarity index 100% rename from src/Web/Avalonia.Web/webapp/modules/storage.ts rename to src/Browser/Avalonia.Browser/webapp/modules/storage.ts diff --git a/src/Web/Avalonia.Web/webapp/modules/storage/indexedDb.ts b/src/Browser/Avalonia.Browser/webapp/modules/storage/indexedDb.ts similarity index 100% rename from src/Web/Avalonia.Web/webapp/modules/storage/indexedDb.ts rename to src/Browser/Avalonia.Browser/webapp/modules/storage/indexedDb.ts diff --git a/src/Web/Avalonia.Web/webapp/modules/storage/storageItem.ts b/src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts similarity index 100% rename from src/Web/Avalonia.Web/webapp/modules/storage/storageItem.ts rename to src/Browser/Avalonia.Browser/webapp/modules/storage/storageItem.ts diff --git a/src/Web/Avalonia.Web/webapp/modules/storage/storageProvider.ts b/src/Browser/Avalonia.Browser/webapp/modules/storage/storageProvider.ts similarity index 100% rename from src/Web/Avalonia.Web/webapp/modules/storage/storageProvider.ts rename to src/Browser/Avalonia.Browser/webapp/modules/storage/storageProvider.ts diff --git a/src/Browser/Avalonia.Browser/webapp/package-lock.json b/src/Browser/Avalonia.Browser/webapp/package-lock.json new file mode 100644 index 0000000000..06e94629d7 --- /dev/null +++ b/src/Browser/Avalonia.Browser/webapp/package-lock.json @@ -0,0 +1,5496 @@ +{ + "name": "avalonia.browser", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "avalonia.browser", + "devDependencies": { + "@types/emscripten": "^1.39.6", + "@types/wicg-file-system-access": "^2020.9.5", + "@typescript-eslint/eslint-plugin": "^5.38.1", + "esbuild": "^0.15.7", + "eslint": "^8.24.0", + "eslint-config-standard-with-typescript": "^23.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-n": "^15.3.0", + "eslint-plugin-promise": "^6.0.1", + "npm-run-all": "^4.1.5", + "typescript": "^4.8.3" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.9.tgz", + "integrity": "sha512-VZPy/ETF3fBG5PiinIkA0W/tlsvlEgJccyN2DzWZEl0DlVKRbu91PvY2D6Lxgluj4w9QtYHjOWjAT44C+oQ+EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.9.tgz", + "integrity": "sha512-O+NfmkfRrb3uSsTa4jE3WApidSe3N5++fyOVGP1SmMZi4A3BZELkhUUvj5hwmMuNdlpzAZ8iAPz2vmcR7DCFQA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz", + "integrity": "sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.5.tgz", + "integrity": "sha512-XVVDtp+dVvRxMoxSiSfasYaG02VEe1qH5cKgMQJWhol6HwzbcqoCMJi8dAGoYAO57jhUyhI6cWuRiTcRaDaYug==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/emscripten": { + "version": "1.39.6", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.6.tgz", + "integrity": "sha512-H90aoynNhhkQP6DRweEjJp5vfUVdIj7tdPLsu7pq89vODD/lcugKfZOsfgwpvM6XUewEp2N5dCg1Uf3Qe55Dcg==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/wicg-file-system-access": { + "version": "2020.9.5", + "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.5.tgz", + "integrity": "sha512-UYK244awtmcUYQfs7FR8710MJcefL2WvkyHMjA8yJzxd1mo0Gfn88sRZ1Bls7hiUhA2w7ne1gpJ9T5g3G0wOyA==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz", + "integrity": "sha512-ky7EFzPhqz3XlhS7vPOoMDaQnQMn+9o5ICR9CPr/6bw8HrFkzhMSxuA3gRfiJVvs7geYrSeawGJjZoZQKCOglQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.38.1", + "@typescript-eslint/type-utils": "5.38.1", + "@typescript-eslint/utils": "5.38.1", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.1.tgz", + "integrity": "sha512-LDqxZBVFFQnQRz9rUZJhLmox+Ep5kdUmLatLQnCRR6523YV+XhRjfYzStQ4MheFA8kMAfUlclHSbu+RKdRwQKw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.38.1", + "@typescript-eslint/types": "5.38.1", + "@typescript-eslint/typescript-estree": "5.38.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.38.1.tgz", + "integrity": "sha512-BfRDq5RidVU3RbqApKmS7RFMtkyWMM50qWnDAkKgQiezRtLKsoyRKIvz1Ok5ilRWeD9IuHvaidaLxvGx/2eqTQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.38.1", + "@typescript-eslint/visitor-keys": "5.38.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.38.1.tgz", + "integrity": "sha512-UU3j43TM66gYtzo15ivK2ZFoDFKKP0k03MItzLdq0zV92CeGCXRfXlfQX5ILdd4/DSpHkSjIgLLLh1NtkOJOAw==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.38.1", + "@typescript-eslint/utils": "5.38.1", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.38.1.tgz", + "integrity": "sha512-QTW1iHq1Tffp9lNfbfPm4WJabbvpyaehQ0SrvVK2yfV79SytD9XDVxqiPvdrv2LK7DGSFo91TB2FgWanbJAZXg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.1.tgz", + "integrity": "sha512-99b5e/Enoe8fKMLdSuwrfH/C0EIbpUWmeEKHmQlGZb8msY33qn1KlkFww0z26o5Omx7EVjzVDCWEfrfCDHfE7g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.38.1", + "@typescript-eslint/visitor-keys": "5.38.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.38.1.tgz", + "integrity": "sha512-oIuUiVxPBsndrN81oP8tXnFa/+EcZ03qLqPDfSZ5xIJVm7A9V0rlkQwwBOAGtrdN70ZKDlKv+l1BeT4eSFxwXA==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.38.1", + "@typescript-eslint/types": "5.38.1", + "@typescript-eslint/typescript-estree": "5.38.1", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.1.tgz", + "integrity": "sha512-bSHr1rRxXt54+j2n4k54p4fj8AHJ49VDWtjpImOpzQj4qjAiOpPni+V1Tyajh19Api1i844F757cur8wH3YvOA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.38.1", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.3.tgz", + "integrity": "sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.6", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.9.tgz", + "integrity": "sha512-OnYr1rkMVxtmMHIAKZLMcEUlJmqcbxBz9QoBU8G9v455na0fuzlT/GLu6l+SRghrk0Mm2fSSciMmzV43Q8e0Gg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.15.9", + "@esbuild/linux-loong64": "0.15.9", + "esbuild-android-64": "0.15.9", + "esbuild-android-arm64": "0.15.9", + "esbuild-darwin-64": "0.15.9", + "esbuild-darwin-arm64": "0.15.9", + "esbuild-freebsd-64": "0.15.9", + "esbuild-freebsd-arm64": "0.15.9", + "esbuild-linux-32": "0.15.9", + "esbuild-linux-64": "0.15.9", + "esbuild-linux-arm": "0.15.9", + "esbuild-linux-arm64": "0.15.9", + "esbuild-linux-mips64le": "0.15.9", + "esbuild-linux-ppc64le": "0.15.9", + "esbuild-linux-riscv64": "0.15.9", + "esbuild-linux-s390x": "0.15.9", + "esbuild-netbsd-64": "0.15.9", + "esbuild-openbsd-64": "0.15.9", + "esbuild-sunos-64": "0.15.9", + "esbuild-windows-32": "0.15.9", + "esbuild-windows-64": "0.15.9", + "esbuild-windows-arm64": "0.15.9" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.9.tgz", + "integrity": "sha512-HQCX7FJn9T4kxZQkhPjNZC7tBWZqJvhlLHPU2SFzrQB/7nDXjmTIFpFTjt7Bd1uFpeXmuwf5h5fZm+x/hLnhbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.9.tgz", + "integrity": "sha512-E6zbLfqbFVCNEKircSHnPiSTsm3fCRxeIMPfrkS33tFjIAoXtwegQfVZqMGR0FlsvVxp2NEDOUz+WW48COCjSg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.9.tgz", + "integrity": "sha512-gI7dClcDN/HHVacZhTmGjl0/TWZcGuKJ0I7/xDGJwRQQn7aafZGtvagOFNmuOq+OBFPhlPv1T6JElOXb0unkSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.9.tgz", + "integrity": "sha512-VZIMlcRN29yg/sv7DsDwN+OeufCcoTNaTl3Vnav7dL/nvsApD7uvhVRbgyMzv0zU/PP0xRhhIpTyc7lxEzHGSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.9.tgz", + "integrity": "sha512-uM4z5bTvuAXqPxrI204txhlsPIolQPWRMLenvGuCPZTnnGlCMF2QLs0Plcm26gcskhxewYo9LkkmYSS5Czrb5A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.9.tgz", + "integrity": "sha512-HHDjT3O5gWzicGdgJ5yokZVN9K9KG05SnERwl9nBYZaCjcCgj/sX8Ps1jvoFSfNCO04JSsHSOWo4qvxFuj8FoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.9.tgz", + "integrity": "sha512-AQIdE8FugGt1DkcekKi5ycI46QZpGJ/wqcMr7w6YUmOmp2ohQ8eO4sKUsOxNOvYL7hGEVwkndSyszR6HpVHLFg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.9.tgz", + "integrity": "sha512-4RXjae7g6Qs7StZyiYyXTZXBlfODhb1aBVAjd+ANuPmMhWthQilWo7rFHwJwL7DQu1Fjej2sODAVwLbcIVsAYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.9.tgz", + "integrity": "sha512-3Zf2GVGUOI7XwChH3qrnTOSqfV1V4CAc/7zLVm4lO6JT6wbJrTgEYCCiNSzziSju+J9Jhf9YGWk/26quWPC6yQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.9.tgz", + "integrity": "sha512-a+bTtxJmYmk9d+s2W4/R1SYKDDAldOKmWjWP0BnrWtDbvUBNOm++du0ysPju4mZVoEFgS1yLNW+VXnG/4FNwdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.9.tgz", + "integrity": "sha512-Zn9HSylDp89y+TRREMDoGrc3Z4Hs5u56ozZLQCiZAUx2+HdbbXbWdjmw3FdTJ/i7t5Cew6/Q+6kfO3KCcFGlyw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.9.tgz", + "integrity": "sha512-OEiOxNAMH9ENFYqRsWUj3CWyN3V8P3ZXyfNAtX5rlCEC/ERXrCEFCJji/1F6POzsXAzxvUJrTSTCy7G6BhA6Fw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.9.tgz", + "integrity": "sha512-ukm4KsC3QRausEFjzTsOZ/qqazw0YvJsKmfoZZm9QW27OHjk2XKSQGGvx8gIEswft/Sadp03/VZvAaqv5AIwNA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.9.tgz", + "integrity": "sha512-uDOQEH55wQ6ahcIKzQr3VyjGc6Po/xblLGLoUk3fVL1qjlZAibtQr6XRfy5wPJLu/M2o0vQKLq4lyJ2r1tWKcw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.9.tgz", + "integrity": "sha512-yWgxaYTQz+TqX80wXRq6xAtb7GSBAp6gqLKfOdANg9qEmAI1Bxn04IrQr0Mzm4AhxvGKoHzjHjMgXbCCSSDxcw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.9.tgz", + "integrity": "sha512-JmS18acQl4iSAjrEha1MfEmUMN4FcnnrtTaJ7Qg0tDCOcgpPPQRLGsZqhes0vmx8VA6IqRyScqXvaL7+Q0Uf3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.9.tgz", + "integrity": "sha512-UKynGSWpzkPmXW3D2UMOD9BZPIuRaSqphxSCwScfEE05Be3KAmvjsBhht1fLzKpiFVJb0BYMd4jEbWMyJ/z1hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.9.tgz", + "integrity": "sha512-aqXvu4/W9XyTVqO/hw3rNxKE1TcZiEYHPsXM9LwYmKSX9/hjvfIJzXwQBlPcJ/QOxedfoMVH0YnhhQ9Ffb0RGA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.9.tgz", + "integrity": "sha512-zm7h91WUmlS4idMtjvCrEeNhlH7+TNOmqw5dJPJZrgFaxoFyqYG6CKDpdFCQXdyKpD5yvzaQBOMVTCBVKGZDEg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.9.tgz", + "integrity": "sha512-yQEVIv27oauAtvtuhJVfSNMztJJX47ismRS6Sv2QMVV9RM+6xjbMWuuwM2nxr5A2/gj/mu2z9YlQxiwoFRCfZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.24.0.tgz", + "integrity": "sha512-dWFaPhGhTAiPcCgm3f6LI2MBWbogMnTJzFBbhXVRQDJPkr9pGZvVjlVfXd+vyDcWPA2Ic9L2AXPIQM0+vk/cSQ==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.2", + "@humanwhocodes/config-array": "^0.10.5", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@humanwhocodes/module-importer": "^1.0.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-standard": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", + "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-promise": "^6.0.0" + } + }, + "node_modules/eslint-config-standard-with-typescript": { + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-23.0.0.tgz", + "integrity": "sha512-iaaWifImn37Z1OXbNW1es7KI+S7D408F9ys0bpaQf2temeBWlvb0Nc5qHkOgYaRb5QxTZT32GGeN1gtswASOXA==", + "dev": true, + "dependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint-config-standard": "17.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-promise": "^6.0.0", + "typescript": "*" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/eslint-plugin-n": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.3.0.tgz", + "integrity": "sha512-IyzPnEWHypCWasDpxeJnim60jhlumbmq0pubL6IOcnk8u2y53s5QfT8JnXy7skjHJ44yWHRb11PLtDHuu1kg/Q==", + "dev": true, + "dependencies": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.10.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.1.tgz", + "integrity": "sha512-uM4Tgo5u3UWQiroOyDEsYcVMOo7re3zmno0IZmB5auxoaQNIceAbXEkSt8RNrKtaYehARHG06pYK6K1JhtP0Zw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/espree": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-sdsl": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", + "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/npm-run-all/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/npm-run-all/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "dev": true + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "node_modules/string.prototype.padend": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", + "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", + "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.9.tgz", + "integrity": "sha512-VZPy/ETF3fBG5PiinIkA0W/tlsvlEgJccyN2DzWZEl0DlVKRbu91PvY2D6Lxgluj4w9QtYHjOWjAT44C+oQ+EQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.9.tgz", + "integrity": "sha512-O+NfmkfRrb3uSsTa4jE3WApidSe3N5++fyOVGP1SmMZi4A3BZELkhUUvj5hwmMuNdlpzAZ8iAPz2vmcR7DCFQA==", + "dev": true, + "optional": true + }, + "@eslint/eslintrc": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz", + "integrity": "sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.5.tgz", + "integrity": "sha512-XVVDtp+dVvRxMoxSiSfasYaG02VEe1qH5cKgMQJWhol6HwzbcqoCMJi8dAGoYAO57jhUyhI6cWuRiTcRaDaYug==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@types/emscripten": { + "version": "1.39.6", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.6.tgz", + "integrity": "sha512-H90aoynNhhkQP6DRweEjJp5vfUVdIj7tdPLsu7pq89vODD/lcugKfZOsfgwpvM6XUewEp2N5dCg1Uf3Qe55Dcg==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "@types/wicg-file-system-access": { + "version": "2020.9.5", + "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.5.tgz", + "integrity": "sha512-UYK244awtmcUYQfs7FR8710MJcefL2WvkyHMjA8yJzxd1mo0Gfn88sRZ1Bls7hiUhA2w7ne1gpJ9T5g3G0wOyA==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz", + "integrity": "sha512-ky7EFzPhqz3XlhS7vPOoMDaQnQMn+9o5ICR9CPr/6bw8HrFkzhMSxuA3gRfiJVvs7geYrSeawGJjZoZQKCOglQ==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.38.1", + "@typescript-eslint/type-utils": "5.38.1", + "@typescript-eslint/utils": "5.38.1", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/parser": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.1.tgz", + "integrity": "sha512-LDqxZBVFFQnQRz9rUZJhLmox+Ep5kdUmLatLQnCRR6523YV+XhRjfYzStQ4MheFA8kMAfUlclHSbu+RKdRwQKw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.38.1", + "@typescript-eslint/types": "5.38.1", + "@typescript-eslint/typescript-estree": "5.38.1", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.38.1.tgz", + "integrity": "sha512-BfRDq5RidVU3RbqApKmS7RFMtkyWMM50qWnDAkKgQiezRtLKsoyRKIvz1Ok5ilRWeD9IuHvaidaLxvGx/2eqTQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.38.1", + "@typescript-eslint/visitor-keys": "5.38.1" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.38.1.tgz", + "integrity": "sha512-UU3j43TM66gYtzo15ivK2ZFoDFKKP0k03MItzLdq0zV92CeGCXRfXlfQX5ILdd4/DSpHkSjIgLLLh1NtkOJOAw==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.38.1", + "@typescript-eslint/utils": "5.38.1", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.38.1.tgz", + "integrity": "sha512-QTW1iHq1Tffp9lNfbfPm4WJabbvpyaehQ0SrvVK2yfV79SytD9XDVxqiPvdrv2LK7DGSFo91TB2FgWanbJAZXg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.1.tgz", + "integrity": "sha512-99b5e/Enoe8fKMLdSuwrfH/C0EIbpUWmeEKHmQlGZb8msY33qn1KlkFww0z26o5Omx7EVjzVDCWEfrfCDHfE7g==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.38.1", + "@typescript-eslint/visitor-keys": "5.38.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.38.1.tgz", + "integrity": "sha512-oIuUiVxPBsndrN81oP8tXnFa/+EcZ03qLqPDfSZ5xIJVm7A9V0rlkQwwBOAGtrdN70ZKDlKv+l1BeT4eSFxwXA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.38.1", + "@typescript-eslint/types": "5.38.1", + "@typescript-eslint/typescript-estree": "5.38.1", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.1.tgz", + "integrity": "sha512-bSHr1rRxXt54+j2n4k54p4fj8AHJ49VDWtjpImOpzQj4qjAiOpPni+V1Tyajh19Api1i844F757cur8wH3YvOA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.38.1", + "eslint-visitor-keys": "^3.3.0" + } + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array.prototype.flat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "requires": { + "semver": "^7.0.0" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.3.tgz", + "integrity": "sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.6", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "esbuild": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.9.tgz", + "integrity": "sha512-OnYr1rkMVxtmMHIAKZLMcEUlJmqcbxBz9QoBU8G9v455na0fuzlT/GLu6l+SRghrk0Mm2fSSciMmzV43Q8e0Gg==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.15.9", + "@esbuild/linux-loong64": "0.15.9", + "esbuild-android-64": "0.15.9", + "esbuild-android-arm64": "0.15.9", + "esbuild-darwin-64": "0.15.9", + "esbuild-darwin-arm64": "0.15.9", + "esbuild-freebsd-64": "0.15.9", + "esbuild-freebsd-arm64": "0.15.9", + "esbuild-linux-32": "0.15.9", + "esbuild-linux-64": "0.15.9", + "esbuild-linux-arm": "0.15.9", + "esbuild-linux-arm64": "0.15.9", + "esbuild-linux-mips64le": "0.15.9", + "esbuild-linux-ppc64le": "0.15.9", + "esbuild-linux-riscv64": "0.15.9", + "esbuild-linux-s390x": "0.15.9", + "esbuild-netbsd-64": "0.15.9", + "esbuild-openbsd-64": "0.15.9", + "esbuild-sunos-64": "0.15.9", + "esbuild-windows-32": "0.15.9", + "esbuild-windows-64": "0.15.9", + "esbuild-windows-arm64": "0.15.9" + } + }, + "esbuild-android-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.9.tgz", + "integrity": "sha512-HQCX7FJn9T4kxZQkhPjNZC7tBWZqJvhlLHPU2SFzrQB/7nDXjmTIFpFTjt7Bd1uFpeXmuwf5h5fZm+x/hLnhbw==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.9.tgz", + "integrity": "sha512-E6zbLfqbFVCNEKircSHnPiSTsm3fCRxeIMPfrkS33tFjIAoXtwegQfVZqMGR0FlsvVxp2NEDOUz+WW48COCjSg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.9.tgz", + "integrity": "sha512-gI7dClcDN/HHVacZhTmGjl0/TWZcGuKJ0I7/xDGJwRQQn7aafZGtvagOFNmuOq+OBFPhlPv1T6JElOXb0unkSQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.9.tgz", + "integrity": "sha512-VZIMlcRN29yg/sv7DsDwN+OeufCcoTNaTl3Vnav7dL/nvsApD7uvhVRbgyMzv0zU/PP0xRhhIpTyc7lxEzHGSw==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.9.tgz", + "integrity": "sha512-uM4z5bTvuAXqPxrI204txhlsPIolQPWRMLenvGuCPZTnnGlCMF2QLs0Plcm26gcskhxewYo9LkkmYSS5Czrb5A==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.9.tgz", + "integrity": "sha512-HHDjT3O5gWzicGdgJ5yokZVN9K9KG05SnERwl9nBYZaCjcCgj/sX8Ps1jvoFSfNCO04JSsHSOWo4qvxFuj8FoA==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.9.tgz", + "integrity": "sha512-AQIdE8FugGt1DkcekKi5ycI46QZpGJ/wqcMr7w6YUmOmp2ohQ8eO4sKUsOxNOvYL7hGEVwkndSyszR6HpVHLFg==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.9.tgz", + "integrity": "sha512-4RXjae7g6Qs7StZyiYyXTZXBlfODhb1aBVAjd+ANuPmMhWthQilWo7rFHwJwL7DQu1Fjej2sODAVwLbcIVsAYQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.9.tgz", + "integrity": "sha512-3Zf2GVGUOI7XwChH3qrnTOSqfV1V4CAc/7zLVm4lO6JT6wbJrTgEYCCiNSzziSju+J9Jhf9YGWk/26quWPC6yQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.9.tgz", + "integrity": "sha512-a+bTtxJmYmk9d+s2W4/R1SYKDDAldOKmWjWP0BnrWtDbvUBNOm++du0ysPju4mZVoEFgS1yLNW+VXnG/4FNwdQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.9.tgz", + "integrity": "sha512-Zn9HSylDp89y+TRREMDoGrc3Z4Hs5u56ozZLQCiZAUx2+HdbbXbWdjmw3FdTJ/i7t5Cew6/Q+6kfO3KCcFGlyw==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.9.tgz", + "integrity": "sha512-OEiOxNAMH9ENFYqRsWUj3CWyN3V8P3ZXyfNAtX5rlCEC/ERXrCEFCJji/1F6POzsXAzxvUJrTSTCy7G6BhA6Fw==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.9.tgz", + "integrity": "sha512-ukm4KsC3QRausEFjzTsOZ/qqazw0YvJsKmfoZZm9QW27OHjk2XKSQGGvx8gIEswft/Sadp03/VZvAaqv5AIwNA==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.9.tgz", + "integrity": "sha512-uDOQEH55wQ6ahcIKzQr3VyjGc6Po/xblLGLoUk3fVL1qjlZAibtQr6XRfy5wPJLu/M2o0vQKLq4lyJ2r1tWKcw==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.9.tgz", + "integrity": "sha512-yWgxaYTQz+TqX80wXRq6xAtb7GSBAp6gqLKfOdANg9qEmAI1Bxn04IrQr0Mzm4AhxvGKoHzjHjMgXbCCSSDxcw==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.9.tgz", + "integrity": "sha512-JmS18acQl4iSAjrEha1MfEmUMN4FcnnrtTaJ7Qg0tDCOcgpPPQRLGsZqhes0vmx8VA6IqRyScqXvaL7+Q0Uf3A==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.9.tgz", + "integrity": "sha512-UKynGSWpzkPmXW3D2UMOD9BZPIuRaSqphxSCwScfEE05Be3KAmvjsBhht1fLzKpiFVJb0BYMd4jEbWMyJ/z1hQ==", + "dev": true, + "optional": true + }, + "esbuild-windows-32": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.9.tgz", + "integrity": "sha512-aqXvu4/W9XyTVqO/hw3rNxKE1TcZiEYHPsXM9LwYmKSX9/hjvfIJzXwQBlPcJ/QOxedfoMVH0YnhhQ9Ffb0RGA==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.9.tgz", + "integrity": "sha512-zm7h91WUmlS4idMtjvCrEeNhlH7+TNOmqw5dJPJZrgFaxoFyqYG6CKDpdFCQXdyKpD5yvzaQBOMVTCBVKGZDEg==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.15.9", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.9.tgz", + "integrity": "sha512-yQEVIv27oauAtvtuhJVfSNMztJJX47ismRS6Sv2QMVV9RM+6xjbMWuuwM2nxr5A2/gj/mu2z9YlQxiwoFRCfZA==", + "dev": true, + "optional": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.24.0.tgz", + "integrity": "sha512-dWFaPhGhTAiPcCgm3f6LI2MBWbogMnTJzFBbhXVRQDJPkr9pGZvVjlVfXd+vyDcWPA2Ic9L2AXPIQM0+vk/cSQ==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.2", + "@humanwhocodes/config-array": "^0.10.5", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@humanwhocodes/module-importer": "^1.0.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + } + } + }, + "eslint-config-standard": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", + "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "dev": true, + "requires": {} + }, + "eslint-config-standard-with-typescript": { + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-23.0.0.tgz", + "integrity": "sha512-iaaWifImn37Z1OXbNW1es7KI+S7D408F9ys0bpaQf2temeBWlvb0Nc5qHkOgYaRb5QxTZT32GGeN1gtswASOXA==", + "dev": true, + "requires": { + "@typescript-eslint/parser": "^5.0.0", + "eslint-config-standard": "17.0.0" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "eslint-plugin-n": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.3.0.tgz", + "integrity": "sha512-IyzPnEWHypCWasDpxeJnim60jhlumbmq0pubL6IOcnk8u2y53s5QfT8JnXy7skjHJ44yWHRb11PLtDHuu1kg/Q==", + "dev": true, + "requires": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.10.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.7" + } + }, + "eslint-plugin-promise": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.1.tgz", + "integrity": "sha512-uM4Tgo5u3UWQiroOyDEsYcVMOo7re3zmno0IZmB5auxoaQNIceAbXEkSt8RNrKtaYehARHG06pYK6K1JhtP0Zw==", + "dev": true, + "requires": {} + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "js-sdsl": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", + "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + } + } + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "string.prototype.padend": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", + "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typescript": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", + "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", + "dev": true + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/src/Web/Avalonia.Web/webapp/package.json b/src/Browser/Avalonia.Browser/webapp/package.json similarity index 95% rename from src/Web/Avalonia.Web/webapp/package.json rename to src/Browser/Avalonia.Browser/webapp/package.json index 8845dec604..05a3976ccc 100644 --- a/src/Web/Avalonia.Web/webapp/package.json +++ b/src/Browser/Avalonia.Browser/webapp/package.json @@ -1,5 +1,5 @@ { - "name": "avalonia.web", + "name": "avalonia.browser", "scripts": { "typecheck": "npx tsc -noEmit", "eslint": "npx eslint . --fix", diff --git a/src/Web/Avalonia.Web/webapp/tsconfig.json b/src/Browser/Avalonia.Browser/webapp/tsconfig.json similarity index 100% rename from src/Web/Avalonia.Web/webapp/tsconfig.json rename to src/Browser/Avalonia.Browser/webapp/tsconfig.json diff --git a/src/Web/Avalonia.Web/webapp/types/dotnet.d.ts b/src/Browser/Avalonia.Browser/webapp/types/dotnet.d.ts similarity index 100% rename from src/Web/Avalonia.Web/webapp/types/dotnet.d.ts rename to src/Browser/Avalonia.Browser/webapp/types/dotnet.d.ts diff --git a/src/Web/Avalonia.Web/webapp/package-lock.json b/src/Web/Avalonia.Web/webapp/package-lock.json deleted file mode 100644 index 947f7e12e7..0000000000 --- a/src/Web/Avalonia.Web/webapp/package-lock.json +++ /dev/null @@ -1,2234 +0,0 @@ -{ - "name": "avalonia.web", - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "@esbuild/android-arm": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.9.tgz", - "integrity": "sha512-VZPy/ETF3fBG5PiinIkA0W/tlsvlEgJccyN2DzWZEl0DlVKRbu91PvY2D6Lxgluj4w9QtYHjOWjAT44C+oQ+EQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.9.tgz", - "integrity": "sha512-O+NfmkfRrb3uSsTa4jE3WApidSe3N5++fyOVGP1SmMZi4A3BZELkhUUvj5hwmMuNdlpzAZ8iAPz2vmcR7DCFQA==", - "dev": true, - "optional": true - }, - "@eslint/eslintrc": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz", - "integrity": "sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - } - }, - "@humanwhocodes/config-array": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.5.tgz", - "integrity": "sha512-XVVDtp+dVvRxMoxSiSfasYaG02VEe1qH5cKgMQJWhol6HwzbcqoCMJi8dAGoYAO57jhUyhI6cWuRiTcRaDaYug==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/gitignore-to-minimatch": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", - "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", - "dev": true - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@types/emscripten": { - "version": "1.39.6", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.6.tgz", - "integrity": "sha512-H90aoynNhhkQP6DRweEjJp5vfUVdIj7tdPLsu7pq89vODD/lcugKfZOsfgwpvM6XUewEp2N5dCg1Uf3Qe55Dcg==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, - "@types/wicg-file-system-access": { - "version": "2020.9.5", - "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.5.tgz", - "integrity": "sha512-UYK244awtmcUYQfs7FR8710MJcefL2WvkyHMjA8yJzxd1mo0Gfn88sRZ1Bls7hiUhA2w7ne1gpJ9T5g3G0wOyA==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz", - "integrity": "sha512-ky7EFzPhqz3XlhS7vPOoMDaQnQMn+9o5ICR9CPr/6bw8HrFkzhMSxuA3gRfiJVvs7geYrSeawGJjZoZQKCOglQ==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.38.1", - "@typescript-eslint/type-utils": "5.38.1", - "@typescript-eslint/utils": "5.38.1", - "debug": "^4.3.4", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/parser": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.1.tgz", - "integrity": "sha512-LDqxZBVFFQnQRz9rUZJhLmox+Ep5kdUmLatLQnCRR6523YV+XhRjfYzStQ4MheFA8kMAfUlclHSbu+RKdRwQKw==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.38.1", - "@typescript-eslint/types": "5.38.1", - "@typescript-eslint/typescript-estree": "5.38.1", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.38.1.tgz", - "integrity": "sha512-BfRDq5RidVU3RbqApKmS7RFMtkyWMM50qWnDAkKgQiezRtLKsoyRKIvz1Ok5ilRWeD9IuHvaidaLxvGx/2eqTQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.38.1", - "@typescript-eslint/visitor-keys": "5.38.1" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.38.1.tgz", - "integrity": "sha512-UU3j43TM66gYtzo15ivK2ZFoDFKKP0k03MItzLdq0zV92CeGCXRfXlfQX5ILdd4/DSpHkSjIgLLLh1NtkOJOAw==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "5.38.1", - "@typescript-eslint/utils": "5.38.1", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.38.1.tgz", - "integrity": "sha512-QTW1iHq1Tffp9lNfbfPm4WJabbvpyaehQ0SrvVK2yfV79SytD9XDVxqiPvdrv2LK7DGSFo91TB2FgWanbJAZXg==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.1.tgz", - "integrity": "sha512-99b5e/Enoe8fKMLdSuwrfH/C0EIbpUWmeEKHmQlGZb8msY33qn1KlkFww0z26o5Omx7EVjzVDCWEfrfCDHfE7g==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.38.1", - "@typescript-eslint/visitor-keys": "5.38.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/utils": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.38.1.tgz", - "integrity": "sha512-oIuUiVxPBsndrN81oP8tXnFa/+EcZ03qLqPDfSZ5xIJVm7A9V0rlkQwwBOAGtrdN70ZKDlKv+l1BeT4eSFxwXA==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.38.1", - "@typescript-eslint/types": "5.38.1", - "@typescript-eslint/typescript-estree": "5.38.1", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.1.tgz", - "integrity": "sha512-bSHr1rRxXt54+j2n4k54p4fj8AHJ49VDWtjpImOpzQj4qjAiOpPni+V1Tyajh19Api1i844F757cur8wH3YvOA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.38.1", - "eslint-visitor-keys": "^3.3.0" - } - }, - "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "array-includes": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", - "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "array.prototype.flat": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", - "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "builtins": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", - "dev": true, - "requires": { - "semver": "^7.0.0" - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.3.tgz", - "integrity": "sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.6", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - } - }, - "es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "esbuild": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.9.tgz", - "integrity": "sha512-OnYr1rkMVxtmMHIAKZLMcEUlJmqcbxBz9QoBU8G9v455na0fuzlT/GLu6l+SRghrk0Mm2fSSciMmzV43Q8e0Gg==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.15.9", - "@esbuild/linux-loong64": "0.15.9", - "esbuild-android-64": "0.15.9", - "esbuild-android-arm64": "0.15.9", - "esbuild-darwin-64": "0.15.9", - "esbuild-darwin-arm64": "0.15.9", - "esbuild-freebsd-64": "0.15.9", - "esbuild-freebsd-arm64": "0.15.9", - "esbuild-linux-32": "0.15.9", - "esbuild-linux-64": "0.15.9", - "esbuild-linux-arm": "0.15.9", - "esbuild-linux-arm64": "0.15.9", - "esbuild-linux-mips64le": "0.15.9", - "esbuild-linux-ppc64le": "0.15.9", - "esbuild-linux-riscv64": "0.15.9", - "esbuild-linux-s390x": "0.15.9", - "esbuild-netbsd-64": "0.15.9", - "esbuild-openbsd-64": "0.15.9", - "esbuild-sunos-64": "0.15.9", - "esbuild-windows-32": "0.15.9", - "esbuild-windows-64": "0.15.9", - "esbuild-windows-arm64": "0.15.9" - } - }, - "esbuild-android-64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.9.tgz", - "integrity": "sha512-HQCX7FJn9T4kxZQkhPjNZC7tBWZqJvhlLHPU2SFzrQB/7nDXjmTIFpFTjt7Bd1uFpeXmuwf5h5fZm+x/hLnhbw==", - "dev": true, - "optional": true - }, - "esbuild-android-arm64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.9.tgz", - "integrity": "sha512-E6zbLfqbFVCNEKircSHnPiSTsm3fCRxeIMPfrkS33tFjIAoXtwegQfVZqMGR0FlsvVxp2NEDOUz+WW48COCjSg==", - "dev": true, - "optional": true - }, - "esbuild-darwin-64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.9.tgz", - "integrity": "sha512-gI7dClcDN/HHVacZhTmGjl0/TWZcGuKJ0I7/xDGJwRQQn7aafZGtvagOFNmuOq+OBFPhlPv1T6JElOXb0unkSQ==", - "dev": true, - "optional": true - }, - "esbuild-darwin-arm64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.9.tgz", - "integrity": "sha512-VZIMlcRN29yg/sv7DsDwN+OeufCcoTNaTl3Vnav7dL/nvsApD7uvhVRbgyMzv0zU/PP0xRhhIpTyc7lxEzHGSw==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.9.tgz", - "integrity": "sha512-uM4z5bTvuAXqPxrI204txhlsPIolQPWRMLenvGuCPZTnnGlCMF2QLs0Plcm26gcskhxewYo9LkkmYSS5Czrb5A==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-arm64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.9.tgz", - "integrity": "sha512-HHDjT3O5gWzicGdgJ5yokZVN9K9KG05SnERwl9nBYZaCjcCgj/sX8Ps1jvoFSfNCO04JSsHSOWo4qvxFuj8FoA==", - "dev": true, - "optional": true - }, - "esbuild-linux-32": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.9.tgz", - "integrity": "sha512-AQIdE8FugGt1DkcekKi5ycI46QZpGJ/wqcMr7w6YUmOmp2ohQ8eO4sKUsOxNOvYL7hGEVwkndSyszR6HpVHLFg==", - "dev": true, - "optional": true - }, - "esbuild-linux-64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.9.tgz", - "integrity": "sha512-4RXjae7g6Qs7StZyiYyXTZXBlfODhb1aBVAjd+ANuPmMhWthQilWo7rFHwJwL7DQu1Fjej2sODAVwLbcIVsAYQ==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.9.tgz", - "integrity": "sha512-3Zf2GVGUOI7XwChH3qrnTOSqfV1V4CAc/7zLVm4lO6JT6wbJrTgEYCCiNSzziSju+J9Jhf9YGWk/26quWPC6yQ==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.9.tgz", - "integrity": "sha512-a+bTtxJmYmk9d+s2W4/R1SYKDDAldOKmWjWP0BnrWtDbvUBNOm++du0ysPju4mZVoEFgS1yLNW+VXnG/4FNwdQ==", - "dev": true, - "optional": true - }, - "esbuild-linux-mips64le": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.9.tgz", - "integrity": "sha512-Zn9HSylDp89y+TRREMDoGrc3Z4Hs5u56ozZLQCiZAUx2+HdbbXbWdjmw3FdTJ/i7t5Cew6/Q+6kfO3KCcFGlyw==", - "dev": true, - "optional": true - }, - "esbuild-linux-ppc64le": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.9.tgz", - "integrity": "sha512-OEiOxNAMH9ENFYqRsWUj3CWyN3V8P3ZXyfNAtX5rlCEC/ERXrCEFCJji/1F6POzsXAzxvUJrTSTCy7G6BhA6Fw==", - "dev": true, - "optional": true - }, - "esbuild-linux-riscv64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.9.tgz", - "integrity": "sha512-ukm4KsC3QRausEFjzTsOZ/qqazw0YvJsKmfoZZm9QW27OHjk2XKSQGGvx8gIEswft/Sadp03/VZvAaqv5AIwNA==", - "dev": true, - "optional": true - }, - "esbuild-linux-s390x": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.9.tgz", - "integrity": "sha512-uDOQEH55wQ6ahcIKzQr3VyjGc6Po/xblLGLoUk3fVL1qjlZAibtQr6XRfy5wPJLu/M2o0vQKLq4lyJ2r1tWKcw==", - "dev": true, - "optional": true - }, - "esbuild-netbsd-64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.9.tgz", - "integrity": "sha512-yWgxaYTQz+TqX80wXRq6xAtb7GSBAp6gqLKfOdANg9qEmAI1Bxn04IrQr0Mzm4AhxvGKoHzjHjMgXbCCSSDxcw==", - "dev": true, - "optional": true - }, - "esbuild-openbsd-64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.9.tgz", - "integrity": "sha512-JmS18acQl4iSAjrEha1MfEmUMN4FcnnrtTaJ7Qg0tDCOcgpPPQRLGsZqhes0vmx8VA6IqRyScqXvaL7+Q0Uf3A==", - "dev": true, - "optional": true - }, - "esbuild-sunos-64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.9.tgz", - "integrity": "sha512-UKynGSWpzkPmXW3D2UMOD9BZPIuRaSqphxSCwScfEE05Be3KAmvjsBhht1fLzKpiFVJb0BYMd4jEbWMyJ/z1hQ==", - "dev": true, - "optional": true - }, - "esbuild-windows-32": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.9.tgz", - "integrity": "sha512-aqXvu4/W9XyTVqO/hw3rNxKE1TcZiEYHPsXM9LwYmKSX9/hjvfIJzXwQBlPcJ/QOxedfoMVH0YnhhQ9Ffb0RGA==", - "dev": true, - "optional": true - }, - "esbuild-windows-64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.9.tgz", - "integrity": "sha512-zm7h91WUmlS4idMtjvCrEeNhlH7+TNOmqw5dJPJZrgFaxoFyqYG6CKDpdFCQXdyKpD5yvzaQBOMVTCBVKGZDEg==", - "dev": true, - "optional": true - }, - "esbuild-windows-arm64": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.9.tgz", - "integrity": "sha512-yQEVIv27oauAtvtuhJVfSNMztJJX47ismRS6Sv2QMVV9RM+6xjbMWuuwM2nxr5A2/gj/mu2z9YlQxiwoFRCfZA==", - "dev": true, - "optional": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.24.0.tgz", - "integrity": "sha512-dWFaPhGhTAiPcCgm3f6LI2MBWbogMnTJzFBbhXVRQDJPkr9pGZvVjlVfXd+vyDcWPA2Ic9L2AXPIQM0+vk/cSQ==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.3.2", - "@humanwhocodes/config-array": "^0.10.5", - "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", - "@humanwhocodes/module-importer": "^1.0.1", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "dependencies": { - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - } - } - }, - "eslint-config-standard": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", - "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", - "dev": true - }, - "eslint-config-standard-with-typescript": { - "version": "23.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-23.0.0.tgz", - "integrity": "sha512-iaaWifImn37Z1OXbNW1es7KI+S7D408F9ys0bpaQf2temeBWlvb0Nc5qHkOgYaRb5QxTZT32GGeN1gtswASOXA==", - "dev": true, - "requires": { - "@typescript-eslint/parser": "^5.0.0", - "eslint-config-standard": "17.0.0" - } - }, - "eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", - "dev": true, - "requires": { - "debug": "^3.2.7" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-plugin-es": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", - "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", - "dev": true, - "requires": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-plugin-import": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", - "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", - "dev": true, - "requires": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", - "has": "^1.0.3", - "is-core-module": "^2.8.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "eslint-plugin-n": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.3.0.tgz", - "integrity": "sha512-IyzPnEWHypCWasDpxeJnim60jhlumbmq0pubL6IOcnk8u2y53s5QfT8JnXy7skjHJ44yWHRb11PLtDHuu1kg/Q==", - "dev": true, - "requires": { - "builtins": "^5.0.1", - "eslint-plugin-es": "^4.1.0", - "eslint-utils": "^3.0.0", - "ignore": "^5.1.1", - "is-core-module": "^2.10.0", - "minimatch": "^3.1.2", - "resolve": "^1.22.1", - "semver": "^7.3.7" - } - }, - "eslint-plugin-promise": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.1.tgz", - "integrity": "sha512-uM4Tgo5u3UWQiroOyDEsYcVMOo7re3zmno0IZmB5auxoaQNIceAbXEkSt8RNrKtaYehARHG06pYK6K1JhtP0Zw==", - "dev": true - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", - "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", - "dev": true, - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true - }, - "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "js-sdsl": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", - "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "npm-run-all": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", - "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "dependencies": { - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - } - } - }, - "regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - } - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shell-quote": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", - "dev": true - }, - "string.prototype.padend": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", - "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", - "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "typescript": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", - "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", - "dev": true - }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} From cd83f8558f29a39351ef0315ed81bcbe154d1689 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 18 Nov 2022 00:47:23 -0500 Subject: [PATCH 113/136] Changes after the reivew --- .../XamlCompilerTaskExecutor.cs | 194 ++++++++++-------- .../AvaloniaXamlIlAssetIncludeTransformer.cs | 25 +-- .../AvaloniaXamlIlWellKnownTypes.cs | 4 + 3 files changed, 130 insertions(+), 93 deletions(-) diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 75abfe352f..974f8485c0 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -469,88 +469,9 @@ namespace Avalonia.Build.Tasks foreach (var (populateMethod, resourceFilePath) in populateMethodsToTransform) { - foreach (var instruction in populateMethod.Body.Instructions.ToArray()) + if (!TransformXamlIncludes(engine, typeSystem, populateMethod, resourceFilePath, createRootServiceProviderMethod)) { - const string resolveStyleIncludeName = "ResolveStyleInclude"; - const string resolveResourceInclude = "ResolveResourceInclude"; - if (instruction.OpCode == OpCodes.Call - && instruction.Operand is MethodReference - { - Name: resolveStyleIncludeName or resolveResourceInclude, - DeclaringType: { Name: "XamlIlRuntimeHelpers" } - }) - { - int lineNumber = 0, linePosition = 0; - bool instructionsModified = false; - try - { - var assetSource = (string)instruction.Previous.Previous.Previous.Operand; - lineNumber = Convert.ToInt32(instruction.Previous.Previous.Operand); - linePosition = Convert.ToInt32(instruction.Previous.Operand); - - var index = populateMethod.Body.Instructions.IndexOf(instruction); - - assetSource = assetSource.Replace("avares://", ""); - - var assemblyNameSeparator = assetSource.IndexOf('/'); - var fileNameSeparator = assetSource.LastIndexOf('/'); - if (assemblyNameSeparator < 0 || fileNameSeparator < 0) - { - throw new InvalidProgramException( - $"Invalid asset source \"{assetSource}\". It must contain assembly name and relative path."); - } - - var assetAssemblyName = assetSource.Substring(0, assemblyNameSeparator); - var assetAssembly = typeSystem.FindAssembly(assetAssemblyName) - ?? throw new InvalidProgramException($"Unable to resolve assembly \"{assetAssemblyName}\""); - - var fileName = Path.GetFileNameWithoutExtension(assetSource.Replace('/', '.')); - if (assetAssembly.FindType(fileName) is { } type - && type.FindConstructor() is { } ctor) - { - var ctorMethod = - asm.MainModule.ImportReference(typeSystem.GetMethodReference(ctor)); - instructionsModified = true; - populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop); - populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop); - populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Nop); - populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Newobj, ctorMethod); - } - else - { - var resources = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources") - ?? throw new InvalidOperationException($"Unable to resolve \"!AvaloniaResources\" type on \"{assetAssemblyName}\" assembly"); - - var relativeName = "Build:" + assetSource.Substring(assemblyNameSeparator); - var buildMethod = resources.FindMethod(m => m.Name == relativeName) - ?? throw new InvalidOperationException($"Unable to resolve build method \"{relativeName}\" resource on the \"{assetAssemblyName}\" assembly"); - - var methodReference = asm.MainModule.ImportReference(typeSystem.GetMethodReference(buildMethod)); - instructionsModified = true; - populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop); - populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop); - populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Call, createRootServiceProviderMethod); - populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Call, methodReference); - } - } - catch (Exception e) - { - if (instructionsModified) - { - engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", resourceFilePath, - lineNumber, linePosition, lineNumber, linePosition, - e.Message, "", "Avalonia")); - return false; - } - else - { - engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "XAMLIL", - resourceFilePath, - lineNumber, linePosition, lineNumber, linePosition, - e.Message, "", "Avalonia")); - } - } - } + return false; } } @@ -630,5 +551,116 @@ namespace Avalonia.Build.Tasks return true; } + + private static bool TransformXamlIncludes( + IBuildEngine engine, CecilTypeSystem typeSystem, + MethodDefinition populateMethod, string resourceFilePath, + MethodReference createRootServiceProviderMethod) + { + var asm = typeSystem.TargetAssemblyDefinition; + foreach (var instruction in populateMethod.Body.Instructions.ToArray()) + { + const string resolveStyleIncludeName = "ResolveStyleInclude"; + const string resolveResourceInclude = "ResolveResourceInclude"; + if (instruction.OpCode == OpCodes.Call + && instruction.Operand is MethodReference + { + Name: resolveStyleIncludeName or resolveResourceInclude, + DeclaringType: { Name: "XamlIlRuntimeHelpers" } + }) + { + int lineNumber = 0, linePosition = 0; + bool instructionsModified = false; + try + { + var assetSource = (string)instruction.Previous.Previous.Previous.Operand; + lineNumber = GetConstValue(instruction.Previous.Previous); + linePosition = GetConstValue(instruction.Previous); + + var index = populateMethod.Body.Instructions.IndexOf(instruction); + + assetSource = assetSource.Replace("avares://", ""); + + var assemblyNameSeparator = assetSource.IndexOf('/'); + var fileNameSeparator = assetSource.LastIndexOf('/'); + if (assemblyNameSeparator < 0 || fileNameSeparator < 0) + { + throw new InvalidProgramException( + $"Invalid asset source \"{assetSource}\". It must contain assembly name and relative path."); + } + + var assetAssemblyName = assetSource.Substring(0, assemblyNameSeparator); + var assetAssembly = typeSystem.FindAssembly(assetAssemblyName) + ?? throw new InvalidProgramException( + $"Unable to resolve assembly \"{assetAssemblyName}\""); + + var fileName = Path.GetFileNameWithoutExtension(assetSource.Replace('/', '.')); + if (assetAssembly.FindType(fileName) is { } type + && type.FindConstructor() is { } ctor) + { + var ctorMethod = + asm.MainModule.ImportReference(typeSystem.GetMethodReference(ctor)); + instructionsModified = true; + populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop); + populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop); + populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Nop); + populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Newobj, ctorMethod); + } + else + { + var resources = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources") + ?? throw new InvalidOperationException( + $"Unable to resolve \"!AvaloniaResources\" type on \"{assetAssemblyName}\" assembly"); + + var relativeName = "Build:" + assetSource.Substring(assemblyNameSeparator); + var buildMethod = resources.FindMethod(m => m.Name == relativeName) + ?? throw new InvalidOperationException( + $"Unable to resolve build method \"{relativeName}\" resource on the \"{assetAssemblyName}\" assembly"); + + var methodReference = asm.MainModule.ImportReference(typeSystem.GetMethodReference(buildMethod)); + instructionsModified = true; + populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop); + populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop); + populateMethod.Body.Instructions[index - 1] = + Instruction.Create(OpCodes.Call, createRootServiceProviderMethod); + populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Call, methodReference); + } + } + catch (Exception e) + { + if (instructionsModified) + { + engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", resourceFilePath, + lineNumber, linePosition, lineNumber, linePosition, + e.Message, "", "Avalonia")); + return false; + } + else + { + engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "XAMLIL", + resourceFilePath, + lineNumber, linePosition, lineNumber, linePosition, + e.Message, "", "Avalonia")); + } + } + + static int GetConstValue(Instruction instruction) + { + if (instruction.OpCode is { Code : >= Code.Ldc_I4_0 and <= Code.Ldc_I4_8 }) + { + return instruction.OpCode.Code - Code.Ldc_I4_0; + } + if (instruction.Operand is not null) + { + return Convert.ToInt32(instruction.Operand); + } + + return 0; + } + } + } + + return true; + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs index fa878ce72c..1572a4f140 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs @@ -10,28 +10,28 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer { - private const string StyleIncludeName = "StyleInclude"; - private const string ResourceIncludeName = "ResourceInclude"; - public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) { if (node is not XamlAstObjectNode objectNode - || objectNode.Type.GetClrType() is not {Name: StyleIncludeName or ResourceIncludeName} objectNodeType) + || (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude + && objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude)) { return node; } + var nodeTypeName = objectNode.Type.GetClrType().Name; + var sourceProperty = objectNode.Children.OfType().FirstOrDefault(n => n.Property.GetClrProperty().Name == "Source"); var directives = objectNode.Children.OfType().ToList(); if (sourceProperty is null || objectNode.Children.Count != (directives.Count + 1)) { - // Don't transform node with any other property, as we don't know how to transform them. - return node; + throw new XamlParseException($"Unexpected property on the {nodeTypeName} node", node); } if (sourceProperty.Values.OfType().FirstOrDefault() is not { } sourceTextNode) { + // TODO: make it a compiler warning // Source value can be set with markup extension instead of a text node, we don't support it here yet. return node; } @@ -39,18 +39,19 @@ internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer var originalAssetPath = sourceTextNode.Text; if (!(originalAssetPath.StartsWith("avares://") || originalAssetPath.StartsWith("/"))) { - // Only "avares" protocol supported or relative paths. - return node; + throw new XamlParseException( + $"{nodeTypeName}.Source supports only \"avares://\" absolute paths or relative paths starting with \"/\"", + sourceTextNode); } - var runtimeHelpers = context.Configuration.TypeSystem.FindType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers"); - var markerMethodName = "Resolve" + objectNodeType.Name; + var runtimeHelpers = context.GetAvaloniaTypes().RuntimeHelpers; + var markerMethodName = "Resolve" + nodeTypeName; var markerMethod = runtimeHelpers.FindMethod(m => m.Name == markerMethodName && m.Parameters.Count == 3); if (markerMethod is null) { - throw new XamlParseException($"Marker method \"{markerMethodName}\" was not found for the \"{objectNodeType.Name}\" node", node); + throw new XamlParseException($"Marker method \"{markerMethodName}\" was not found for the \"{nodeTypeName}\" node", node); } - + return new XamlValueWithManipulationNode( node, new AssetIncludeMethodNode(node, markerMethod, originalAssetPath), diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index b5a9326a7d..dd37ae6c93 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -103,6 +103,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType TextTrimming { get; } public IXamlType ISetter { get; } public IXamlType IStyle { get; } + public IXamlType StyleInclude { get; } + public IXamlType ResourceInclude { get; } public IXamlType IResourceDictionary { get; } public IXamlType ResourceDictionary { get; } public IXamlMethod ResourceDictionaryDeferredAdd { get; } @@ -234,6 +236,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming"); ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter"); IStyle = cfg.TypeSystem.GetType("Avalonia.Styling.IStyle"); + StyleInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.StyleInclude"); + ResourceInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.ResourceInclude"); IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary"); ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary"); ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object, From 82a6d1431b621fe96a78403e01236e741a70cc33 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 18 Nov 2022 01:34:20 -0500 Subject: [PATCH 114/136] Fix tests --- .../Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs index 1572a4f140..377a9e72d9 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs @@ -39,9 +39,7 @@ internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer var originalAssetPath = sourceTextNode.Text; if (!(originalAssetPath.StartsWith("avares://") || originalAssetPath.StartsWith("/"))) { - throw new XamlParseException( - $"{nodeTypeName}.Source supports only \"avares://\" absolute paths or relative paths starting with \"/\"", - sourceTextNode); + return node; } var runtimeHelpers = context.GetAvaloniaTypes().RuntimeHelpers; From 16f3114e61b4fdf535613aae47972d028f7f6766 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Sun, 20 Nov 2022 23:53:34 -0500 Subject: [PATCH 115/136] TextBox programmatic Undo/Redo & CanUndo/CanRedo --- src/Avalonia.Controls/TextBox.cs | 109 ++++++++++++++---- src/Avalonia.Controls/Utils/UndoRedoHelper.cs | 20 ++++ 2 files changed, 109 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index d5b45398e7..b06ec3492c 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -17,7 +17,6 @@ using Avalonia.Controls.Metadata; using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Automation.Peers; -using System.Diagnostics; using Avalonia.Threading; namespace Avalonia.Controls @@ -166,6 +165,18 @@ namespace Avalonia.Controls (o, v) => o.UndoLimit = v, unsetValue: -1); + /// + /// Defines the property + /// + public static readonly DirectProperty CanUndoProperty = + AvaloniaProperty.RegisterDirect(nameof(CanUndo), x => x.CanUndo); + + /// + /// Defines the property + /// + public static readonly DirectProperty CanRedoProperty = + AvaloniaProperty.RegisterDirect(nameof(CanRedo), x => x.CanRedo); + /// /// Defines the event. /// @@ -232,6 +243,8 @@ namespace Avalonia.Controls private bool _canPaste; private string _newLine = Environment.NewLine; private static readonly string[] invalidCharacters = new String[1] { "\u007f" }; + private bool _canUndo; + private bool _canRedo; private int _wordSelectionStart = -1; private int _selectedTextChangesMadeSinceLastUndoSnapshot; @@ -590,6 +603,24 @@ namespace Avalonia.Controls } } + /// + /// Gets a value that indicates whether the undo stack has an action that can be undone + /// + public bool CanUndo + { + get => _canUndo; + private set => SetAndRaise(CanUndoProperty, ref _canUndo, value); + } + + /// + /// Gets a value that indicates whether the redo stack has an action that can be redone + /// + public bool CanRedo + { + get => _canRedo; + private set => SetAndRaise(CanRedoProperty, ref _canRedo, value); + } + public event EventHandler? CopyingToClipboard { add => AddHandler(CopyingToClipboardEvent, value); @@ -943,30 +974,13 @@ namespace Avalonia.Controls } else if (Match(keymap.Undo) && IsUndoEnabled) { - try - { - SnapshotUndoRedo(); - _isUndoingRedoing = true; - _undoRedoHelper.Undo(); - } - finally - { - _isUndoingRedoing = false; - } + Undo(); handled = true; } else if (Match(keymap.Redo) && IsUndoEnabled) { - try - { - _isUndoingRedoing = true; - _undoRedoHelper.Redo(); - } - finally - { - _isUndoingRedoing = false; - } + Redo(); handled = true; } @@ -1703,5 +1717,60 @@ namespace Avalonia.Controls } } } + + /// + /// Undoes the first action in the undo stack + /// + public void Undo() + { + if (IsUndoEnabled && CanUndo) + { + try + { + SnapshotUndoRedo(); + _isUndoingRedoing = true; + _undoRedoHelper.Undo(); + } + finally + { + _isUndoingRedoing = false; + } + } + } + + /// + /// Reapplies the first item on the redo stack + /// + public void Redo() + { + if (IsUndoEnabled && CanRedo) + { + try + { + _isUndoingRedoing = true; + _undoRedoHelper.Redo(); + } + finally + { + _isUndoingRedoing = false; + } + } + } + + /// + /// Called from the UndoRedoHelper when the undo stack is modified + /// + void UndoRedoHelper.IUndoRedoHost.OnUndoStackChanged() + { + CanUndo = _undoRedoHelper.CanUndo; + } + + /// + /// Called from the UndoRedoHelper when the redo stack is modified + /// + void UndoRedoHelper.IUndoRedoHost.OnRedoStackChanged() + { + CanRedo = _undoRedoHelper.CanRedo; + } } } diff --git a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs index 0d5048c080..976dbb5d5f 100644 --- a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs +++ b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs @@ -14,6 +14,10 @@ namespace Avalonia.Controls.Utils public interface IUndoRedoHost { TState UndoRedoState { get; set; } + + void OnUndoStackChanged(); + + void OnRedoStackChanged(); } @@ -28,6 +32,10 @@ namespace Avalonia.Controls.Utils /// public int Limit { get; set; } = 10; + public bool CanUndo => _currentNode?.Previous != null; + + public bool CanRedo => _currentNode?.Next != null; + public UndoRedoHelper(IUndoRedoHost host) { _host = host; @@ -39,6 +47,8 @@ namespace Avalonia.Controls.Utils { _currentNode = _currentNode.Previous; _host.UndoRedoState = _currentNode.Value; + _host.OnUndoStackChanged(); + _host.OnRedoStackChanged(); } } @@ -72,6 +82,8 @@ namespace Avalonia.Controls.Utils { while (_currentNode?.Next != null) _states.Remove(_currentNode.Next); + + _host.OnRedoStackChanged(); } public void Redo() @@ -80,6 +92,8 @@ namespace Avalonia.Controls.Utils { _currentNode = _currentNode.Next; _host.UndoRedoState = _currentNode.Value; + _host.OnRedoStackChanged(); + _host.OnUndoStackChanged(); } } @@ -94,6 +108,9 @@ namespace Avalonia.Controls.Utils _currentNode = _states.Last; if (Limit != -1 && _states.Count > Limit) _states.RemoveFirst(); + + _host.OnUndoStackChanged(); + _host.OnRedoStackChanged(); } } @@ -101,6 +118,9 @@ namespace Avalonia.Controls.Utils { _states.Clear(); _currentNode = null; + + _host.OnUndoStackChanged(); + _host.OnRedoStackChanged(); } } } From 3f1a342e6f3052f0f319924209680dfbe56ae708 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Sun, 20 Nov 2022 23:53:41 -0500 Subject: [PATCH 116/136] Add some tests --- .../TextBoxTests.cs | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 23a330c96f..52a89cd13d 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -866,6 +866,176 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void CanUndo_CanRedo_Is_False_When_Initialized() + { + using (UnitTestApplication.Start(Services)) + { + var tb = new TextBox + { + Template = CreateTemplate(), + Text = "New Text" + }; + + tb.Measure(Size.Infinity); + + Assert.False(tb.CanUndo); + Assert.False(tb.CanRedo); + } + } + + [Fact] + public void CanUndo_CanRedo_and_Programmatic_Undo_Redo_Works() + { + using (UnitTestApplication.Start(Services)) + { + var tb = new TextBox + { + Template = CreateTemplate(), + }; + + tb.Measure(Size.Infinity); + + // See GH #6024 for a bit more insight on when Undo/Redo snapshots are taken: + // - Every 'Space', but only when space is handled in OnKeyDown - Spaces in TextInput event won't work + // - Every 7 chars in a long word + RaiseTextEvent(tb, "ABC"); + RaiseKeyEvent(tb, Key.Space, KeyModifiers.None); + RaiseTextEvent(tb, "DEF"); + RaiseKeyEvent(tb, Key.Space, KeyModifiers.None); + RaiseTextEvent(tb, "123"); + + // NOTE: the spaces won't actually add spaces b/c they're sent only as key events and not Text events + // so our final text is without spaces + Assert.Equal("ABCDEF123", tb.Text); + + Assert.True(tb.CanUndo); + + tb.Undo(); + + // Undo will take us back one step + Assert.Equal("ABCDEF", tb.Text); + + Assert.True(tb.CanRedo); + + tb.Redo(); + + // Redo should restore us + Assert.Equal("ABCDEF123", tb.Text); + } + } + + [Fact] + public void Setting_UndoLimit_Clears_Undo_Redo() + { + using (UnitTestApplication.Start(Services)) + { + var tb = new TextBox + { + Template = CreateTemplate(), + }; + + tb.Measure(Size.Infinity); + + // This is all the same as the above test (CanUndo_CanRedo_and_Programmatic_Undo_Redo_Works) + // We do this to get the undo/redo stacks in a state where both are active + RaiseTextEvent(tb, "ABC"); + RaiseKeyEvent(tb, Key.Space, KeyModifiers.None); + RaiseTextEvent(tb, "DEF"); + RaiseKeyEvent(tb, Key.Space, KeyModifiers.None); + RaiseTextEvent(tb, "123"); + + Assert.Equal("ABCDEF123", tb.Text); + Assert.True(tb.CanUndo); + tb.Undo(); + // Undo will take us back one step + Assert.Equal("ABCDEF", tb.Text); + Assert.True(tb.CanRedo); + tb.Redo(); + // Redo should restore us + Assert.Equal("ABCDEF123", tb.Text); + + // Change the undo limit, this should clear both stacks setting CanUndo and CanRedo to false + tb.UndoLimit = 1; + + Assert.False(tb.CanUndo); + Assert.False(tb.CanRedo); + } + } + + [Fact] + public void Setting_IsUndoEnabled_To_False_Clears_Undo_Redo() + { + using (UnitTestApplication.Start(Services)) + { + var tb = new TextBox + { + Template = CreateTemplate(), + }; + + tb.Measure(Size.Infinity); + + // This is all the same as the above test (CanUndo_CanRedo_and_Programmatic_Undo_Redo_Works) + // We do this to get the undo/redo stacks in a state where both are active + RaiseTextEvent(tb, "ABC"); + RaiseKeyEvent(tb, Key.Space, KeyModifiers.None); + RaiseTextEvent(tb, "DEF"); + RaiseKeyEvent(tb, Key.Space, KeyModifiers.None); + RaiseTextEvent(tb, "123"); + + Assert.Equal("ABCDEF123", tb.Text); + Assert.True(tb.CanUndo); + tb.Undo(); + // Undo will take us back one step + Assert.Equal("ABCDEF", tb.Text); + Assert.True(tb.CanRedo); + tb.Redo(); + // Redo should restore us + Assert.Equal("ABCDEF123", tb.Text); + + // Disable Undo/Redo, this should clear both stacks setting CanUndo and CanRedo to false + tb.IsUndoEnabled = false; + + Assert.False(tb.CanUndo); + Assert.False(tb.CanRedo); + } + } + + [Fact] + public void UndoLimit_Count_Is_Respected() + { + using (UnitTestApplication.Start(Services)) + { + var tb = new TextBox + { + Template = CreateTemplate(), + UndoLimit = 3 // Something small for this test + }; + + tb.Measure(Size.Infinity); + + // Push 3 undoable actions, we should only be able to recover 2 + RaiseTextEvent(tb, "ABC"); + RaiseKeyEvent(tb, Key.Space, KeyModifiers.None); + RaiseTextEvent(tb, "DEF"); + RaiseKeyEvent(tb, Key.Space, KeyModifiers.None); + RaiseTextEvent(tb, "123"); + + Assert.Equal("ABCDEF123", tb.Text); + + // Undo will take us back one step + tb.Undo(); + Assert.Equal("ABCDEF", tb.Text); + + // Undo again + tb.Undo(); + Assert.Equal("ABC", tb.Text); + + // We now should not be able to undo again + Assert.False(tb.CanUndo); + } + } + private static TestServices FocusServices => TestServices.MockThreadingInterface.With( focusManager: new FocusManager(), keyboardDevice: () => new KeyboardDevice(), From 4b089c0e823dfccfab096fe4057a55e5fe9a2b8f Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Mon, 21 Nov 2022 00:16:44 -0500 Subject: [PATCH 117/136] Add some missing xml docs & a little cleanup --- src/Avalonia.Controls/TextBox.cs | 178 +++++++++++++++++- src/Avalonia.Controls/Utils/UndoRedoHelper.cs | 8 +- 2 files changed, 178 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index b06ec3492c..dbab912716 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -28,60 +28,108 @@ namespace Avalonia.Controls [PseudoClasses(":empty")] public class TextBox : TemplatedControl, UndoRedoHelper.IUndoRedoHost { + /// + /// Gets a platform-specific for the Cut action + /// public static KeyGesture? CutGesture { get; } = AvaloniaLocator.Current .GetService()?.Cut.FirstOrDefault(); + /// + /// Gets a platform-specific for the Copy action + /// public static KeyGesture? CopyGesture { get; } = AvaloniaLocator.Current .GetService()?.Copy.FirstOrDefault(); + /// + /// Gets a platform-specific for the Paste action + /// public static KeyGesture? PasteGesture { get; } = AvaloniaLocator.Current .GetService()?.Paste.FirstOrDefault(); + /// + /// Defines the property + /// public static readonly StyledProperty AcceptsReturnProperty = AvaloniaProperty.Register(nameof(AcceptsReturn)); + /// + /// Defines the property + /// public static readonly StyledProperty AcceptsTabProperty = AvaloniaProperty.Register(nameof(AcceptsTab)); + /// + /// Defines the property + /// public static readonly DirectProperty CaretIndexProperty = AvaloniaProperty.RegisterDirect( nameof(CaretIndex), o => o.CaretIndex, (o, v) => o.CaretIndex = v); + /// + /// Defines the property + /// public static readonly StyledProperty IsReadOnlyProperty = AvaloniaProperty.Register(nameof(IsReadOnly)); + /// + /// Defines the property + /// public static readonly StyledProperty PasswordCharProperty = AvaloniaProperty.Register(nameof(PasswordChar)); + /// + /// Defines the property + /// public static readonly StyledProperty SelectionBrushProperty = AvaloniaProperty.Register(nameof(SelectionBrush)); + /// + /// Defines the property + /// public static readonly StyledProperty SelectionForegroundBrushProperty = AvaloniaProperty.Register(nameof(SelectionForegroundBrush)); + /// + /// Defines the property + /// public static readonly StyledProperty CaretBrushProperty = AvaloniaProperty.Register(nameof(CaretBrush)); + /// + /// Defines the property + /// public static readonly DirectProperty SelectionStartProperty = AvaloniaProperty.RegisterDirect( nameof(SelectionStart), o => o.SelectionStart, (o, v) => o.SelectionStart = v); + /// + /// Defines the property + /// public static readonly DirectProperty SelectionEndProperty = AvaloniaProperty.RegisterDirect( nameof(SelectionEnd), o => o.SelectionEnd, (o, v) => o.SelectionEnd = v); + /// + /// Defines the property + /// public static readonly StyledProperty MaxLengthProperty = AvaloniaProperty.Register(nameof(MaxLength), defaultValue: 0); + /// + /// Defines the property + /// public static readonly StyledProperty MaxLinesProperty = AvaloniaProperty.Register(nameof(MaxLines), defaultValue: 0); + /// + /// Defines the property + /// public static readonly DirectProperty TextProperty = TextBlock.TextProperty.AddOwnerWithDataValidation( o => o.Text, @@ -89,6 +137,9 @@ namespace Avalonia.Controls defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true); + /// + /// Defines the property + /// public static readonly StyledProperty TextAlignmentProperty = TextBlock.TextAlignmentProperty.AddOwner(); @@ -119,45 +170,78 @@ namespace Avalonia.Controls public static readonly StyledProperty LetterSpacingProperty = TextBlock.LetterSpacingProperty.AddOwner(); + /// + /// Defines the property + /// public static readonly StyledProperty WatermarkProperty = AvaloniaProperty.Register(nameof(Watermark)); + /// + /// Defines the property + /// public static readonly StyledProperty UseFloatingWatermarkProperty = AvaloniaProperty.Register(nameof(UseFloatingWatermark)); + /// + /// Defines the property + /// public static readonly DirectProperty NewLineProperty = AvaloniaProperty.RegisterDirect(nameof(NewLine), textbox => textbox.NewLine, (textbox, newline) => textbox.NewLine = newline); + /// + /// Defines the property + /// public static readonly StyledProperty InnerLeftContentProperty = AvaloniaProperty.Register(nameof(InnerLeftContent)); + /// + /// Defines the property + /// public static readonly StyledProperty InnerRightContentProperty = AvaloniaProperty.Register(nameof(InnerRightContent)); + /// + /// Defines the property + /// public static readonly StyledProperty RevealPasswordProperty = AvaloniaProperty.Register(nameof(RevealPassword)); + /// + /// Defines the property + /// public static readonly DirectProperty CanCutProperty = AvaloniaProperty.RegisterDirect( nameof(CanCut), o => o.CanCut); + /// + /// Defines the property + /// public static readonly DirectProperty CanCopyProperty = AvaloniaProperty.RegisterDirect( nameof(CanCopy), o => o.CanCopy); + /// + /// Defines the property + /// public static readonly DirectProperty CanPasteProperty = AvaloniaProperty.RegisterDirect( nameof(CanPaste), o => o.CanPaste); + /// + /// Defines the property + /// public static readonly StyledProperty IsUndoEnabledProperty = AvaloniaProperty.Register( nameof(IsUndoEnabled), defaultValue: true); + /// + /// Defines the property + /// public static readonly DirectProperty UndoLimitProperty = AvaloniaProperty.RegisterDirect( nameof(UndoLimit), @@ -212,9 +296,13 @@ namespace Avalonia.Controls RoutedEvent.Register( nameof(TextChanging), RoutingStrategies.Bubble); + /// + /// Stores the state information for available actions in the UndoRedoHelper + /// readonly struct UndoRedoState : IEquatable { public string? Text { get; } + public int CaretPosition { get; } public UndoRedoState(string? text, int caretPosition) @@ -287,18 +375,27 @@ namespace Avalonia.Controls UpdatePseudoclasses(); } + /// + /// Gets or sets a value that determines whether the TextBox allows and displays newline or return characters + /// public bool AcceptsReturn { get => GetValue(AcceptsReturnProperty); set => SetValue(AcceptsReturnProperty, value); } + /// + /// Gets or sets a value that determins whether the TextBox allows and displays tabs + /// public bool AcceptsTab { get => GetValue(AcceptsTabProperty); set => SetValue(AcceptsTabProperty, value); } + /// + /// Gets or sets the index of the text caret + /// public int CaretIndex { get => _caretIndex; @@ -315,36 +412,54 @@ namespace Avalonia.Controls } } + /// + /// Gets or sets a value whether this TextBox is read-only + /// public bool IsReadOnly { get => GetValue(IsReadOnlyProperty); set => SetValue(IsReadOnlyProperty, value); } + /// + /// Gets or sets the that should be used for password masking + /// public char PasswordChar { get => GetValue(PasswordCharProperty); set => SetValue(PasswordCharProperty, value); } + /// + /// Gets or sets a brush that is used to highlight selected text + /// public IBrush? SelectionBrush { get => GetValue(SelectionBrushProperty); set => SetValue(SelectionBrushProperty, value); } + /// + /// Gets or sets a brush that is used for the foreground of selected text + /// public IBrush? SelectionForegroundBrush { get => GetValue(SelectionForegroundBrushProperty); set => SetValue(SelectionForegroundBrushProperty, value); } + /// + /// Gets or sets a brush that is used for the text caret + /// public IBrush? CaretBrush { get => GetValue(CaretBrushProperty); set => SetValue(CaretBrushProperty, value); } + /// + /// Gets or sets the starting position of the text selected in the TextBox + /// public int SelectionStart { get => _selectionStart; @@ -365,6 +480,13 @@ namespace Avalonia.Controls } } + /// + /// Gets or sets the end position of the text selected in the TextBox + /// + /// + /// When the SelectionEnd is equal to , there is no + /// selected text and it marks the caret position + /// public int SelectionEnd { get => _selectionEnd; @@ -384,19 +506,28 @@ namespace Avalonia.Controls } } } - + + /// + /// Gets or sets the maximum character length of the TextBox + /// public int MaxLength { get => GetValue(MaxLengthProperty); set => SetValue(MaxLengthProperty, value); } + /// + /// Gets or sets the maximum number of lines the TextBox can contain + /// public int MaxLines { get => GetValue(MaxLinesProperty); set => SetValue(MaxLinesProperty, value); } + /// + /// Gets or sets the spacing between characters + /// public double LetterSpacing { get => GetValue(LetterSpacingProperty); @@ -412,6 +543,9 @@ namespace Avalonia.Controls set => SetValue(LineHeightProperty, value); } + /// + /// Gets or sets the Text content of the TextBox + /// [Content] public string? Text { @@ -441,6 +575,9 @@ namespace Avalonia.Controls } } + /// + /// Gets or sets the text selected in the TextBox + /// public string SelectedText { get => GetSelection(); @@ -477,6 +614,9 @@ namespace Avalonia.Controls set => SetValue(VerticalContentAlignmentProperty, value); } + /// + /// Gets or sets the of the TextBox + /// public TextAlignment TextAlignment { get => GetValue(TextAlignmentProperty); @@ -503,24 +643,36 @@ namespace Avalonia.Controls set => SetValue(UseFloatingWatermarkProperty, value); } + /// + /// Gets or sets custom content that is positioned on the left side of the text layout box + /// public object InnerLeftContent { get => GetValue(InnerLeftContentProperty); set => SetValue(InnerLeftContentProperty, value); } + /// + /// Gets or sets custom content that is positioned on the right side of the text layout box + /// public object InnerRightContent { get => GetValue(InnerRightContentProperty); set => SetValue(InnerRightContentProperty, value); } + /// + /// Gets or sets whether text masked by should be revealed + /// public bool RevealPassword { get => GetValue(RevealPasswordProperty); set => SetValue(RevealPasswordProperty, value); } + /// + /// Gets or sets the of the TextBox + /// public TextWrapping TextWrapping { get => GetValue(TextWrappingProperty); @@ -580,6 +732,9 @@ namespace Avalonia.Controls set => SetValue(IsUndoEnabledProperty, value); } + /// + /// Gets or sets the maximum number of items that can reside in the Undo stack + /// public int UndoLimit { get => _undoRedoHelper.Limit; @@ -621,18 +776,27 @@ namespace Avalonia.Controls private set => SetAndRaise(CanRedoProperty, ref _canRedo, value); } + /// + /// Raised when content is being copied to the clipboard + /// public event EventHandler? CopyingToClipboard { add => AddHandler(CopyingToClipboardEvent, value); remove => RemoveHandler(CopyingToClipboardEvent, value); } + /// + /// Raised when content is being cut to the clipboard + /// public event EventHandler? CuttingToClipboard { add => AddHandler(CuttingToClipboardEvent, value); remove => RemoveHandler(CuttingToClipboardEvent, value); } + /// + /// Raised when content is being pasted from the clipboard + /// public event EventHandler? PastingFromClipboard { add => AddHandler(PastingFromClipboardEvent, value); @@ -862,6 +1026,9 @@ namespace Avalonia.Controls return text; } + /// + /// Cuts the current text onto the clipboard + /// public async void Cut() { var text = GetSelection(); @@ -882,6 +1049,9 @@ namespace Avalonia.Controls } } + /// + /// Copies the current text onto the clipboard + /// public async void Copy() { var text = GetSelection(); @@ -900,6 +1070,9 @@ namespace Avalonia.Controls } } + /// + /// Pastes the current clipboard text content into the TextBox + /// public async void Paste() { var eventArgs = new RoutedEventArgs(PastingFromClipboardEvent); @@ -1434,6 +1607,9 @@ namespace Avalonia.Controls } } + /// + /// Clears the text in the TextBox + /// public void Clear() { Text = string.Empty; diff --git a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs index 976dbb5d5f..6ff72751a6 100644 --- a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs +++ b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs @@ -1,9 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Utilities; namespace Avalonia.Controls.Utils { @@ -20,8 +15,6 @@ namespace Avalonia.Controls.Utils void OnRedoStackChanged(); } - - private readonly LinkedList _states = new LinkedList(); private LinkedListNode? _currentNode; @@ -65,6 +58,7 @@ namespace Avalonia.Controls.Utils } public bool HasState => _currentNode != null; + public void UpdateLastState(TState state) { if (_states.Last != null) From 0db8d5a2d29bddbea21f919a69f3f827f1bf4933 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 21 Nov 2022 12:24:39 +0100 Subject: [PATCH 118/136] Refactored style attach benchmark. Now tries to simulate an application with a lot of styles applied at different points in the logical tree. Make `StyledElement.ApplyStyling` a public API in order to do this. --- src/Avalonia.Base/StyledElement.cs | 6 +- .../Styling/StyleAttachBenchmark.cs | 47 ------- .../Styling/Style_Apply_Detach_Complex.cs | 126 ++++++++++++++++++ 3 files changed, 131 insertions(+), 48 deletions(-) delete mode 100644 tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs create mode 100644 tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index bba9685ed8..2f3f672d54 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -344,10 +344,14 @@ namespace Avalonia /// Applies styling to the control if the control is initialized and styling is not /// already applied. /// + /// + /// The styling system will automatically apply styling when required, so it should not + /// usually be necessary to call this method manually. + /// /// /// A value indicating whether styling is now applied to the control. /// - protected bool ApplyStyling() + public bool ApplyStyling() { if (_initCount == 0 && !_styled) { diff --git a/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs b/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs deleted file mode 100644 index 7dad517e51..0000000000 --- a/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using Avalonia.Controls; -using Avalonia.Styling; -using Avalonia.UnitTests; -using BenchmarkDotNet.Attributes; - -namespace Avalonia.Benchmarks.Styling -{ - [MemoryDiagnoser] - public class StyleAttachBenchmark : IDisposable - { - private readonly IDisposable _app; - private readonly TestRoot _root; - private readonly TextBox _control; - - public StyleAttachBenchmark() - { - _app = UnitTestApplication.Start( - TestServices.StyledWindow.With( - renderInterface: new NullRenderingPlatform(), - threadingInterface: new NullThreadingPlatform())); - - _root = new TestRoot(true, null) - { - Renderer = new NullRenderer(), - }; - - _control = new TextBox(); - } - - [Benchmark] - [MethodImpl(MethodImplOptions.NoInlining)] - public void AttachTextBoxStyles() - { - var styles = UnitTestApplication.Current.Styles; - - styles.TryAttach(_control, UnitTestApplication.Current); - ((IStyleable)_control).DetachStyles(); - } - - public void Dispose() - { - _app.Dispose(); - } - } -} diff --git a/tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs b/tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs new file mode 100644 index 0000000000..fa8ad00bf8 --- /dev/null +++ b/tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Avalonia.Controls; +using Avalonia.Styling; +using Avalonia.UnitTests; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks.Styling +{ + [MemoryDiagnoser] + public class Style_Apply_Detach_Complex : IDisposable + { + private readonly IDisposable _app; + private readonly TestRoot _root; + private readonly TextBox _control; + + public Style_Apply_Detach_Complex() + { + _app = UnitTestApplication.Start( + TestServices.StyledWindow.With( + renderInterface: new NullRenderingPlatform(), + threadingInterface: new NullThreadingPlatform())); + + // Simulate an application with a lot of styles by creating a tree of nested panels, + // each with a bunch of styles applied. + var (rootPanel, leafPanel) = CreateNestedPanels(10); + + // We're benchmarking how long it takes to apply styles to a TextBox in this situation. + _control = new TextBox(); + leafPanel.Children.Add(_control); + + _root = new TestRoot(true, rootPanel) + { + Renderer = new NullRenderer(), + }; + } + + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public void Apply_Detach_Styles() + { + // Styles will have already been attached when attached to the logical tree, so remove + // the styles first. + if ((string)_control.Tag != "TextBox") + throw new Exception("Invalid benchmark state"); + + ((IStyleable)_control).DetachStyles(); + + if (_control.Tag is not null) + throw new Exception("Invalid benchmark state"); + + // Then re-apply the styles. + _control.ApplyStyling(); + } + + public void Dispose() + { + _app.Dispose(); + } + + private static (Panel, Panel) CreateNestedPanels(int count) + { + var root = new Panel(); + var last = root; + + for (var i = 0; i < count; ++i) + { + var panel = new Panel(); + panel.Styles.AddRange(CreateStyles()); + last.Children.Add(panel); + last = panel; + } + + return (root, last); + } + + private static IEnumerable CreateStyles() + { + var types = new[] + { + typeof(Border), + typeof(Button), + typeof(ButtonSpinner), + typeof(Carousel), + typeof(CheckBox), + typeof(ComboBox), + typeof(ContentControl), + typeof(Expander), + typeof(ItemsControl), + typeof(Label), + typeof(ListBox), + typeof(ProgressBar), + typeof(RadioButton), + typeof(RepeatButton), + typeof(ScrollViewer), + typeof(Slider), + typeof(Spinner), + typeof(SplitView), + typeof(TextBox), + typeof(ToggleSwitch), + typeof(TreeView), + typeof(Viewbox), + typeof(Window), + }; + + foreach (var type in types) + { + yield return new Style(x => x.OfType(type)) + { + Setters = { new Setter(Control.TagProperty, type.Name) } + }; + + yield return new Style(x => x.OfType(type).Class("foo")) + { + Setters = { new Setter(Control.TagProperty, type.Name + " foo") } + }; + + yield return new Style(x => x.OfType(type).Class("bar")) + { + Setters = { new Setter(Control.TagProperty, type.Name + " bar") } + }; + } + } + } +} From 1a338ac087f31b45e4e69513f9d3ab925c690f9c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 10 Nov 2022 10:09:57 +0100 Subject: [PATCH 119/136] Remove IStyler and make style apply internal. - Removes the `IStyler` service and the `Styler` implementation - Moves the logic for applying styles and control themes into `StyledElement` - Removes the style `TryAttach` method from the public API - Removes style caching for now - this will need to be added back --- src/Avalonia.Base/StyledElement.cs | 123 ++++++--- src/Avalonia.Base/Styling/ControlTheme.cs | 27 +- src/Avalonia.Base/Styling/IStyle.cs | 10 - src/Avalonia.Base/Styling/IStyleable.cs | 5 - src/Avalonia.Base/Styling/IStyler.cs | 8 - src/Avalonia.Base/Styling/Style.cs | 54 ++-- src/Avalonia.Base/Styling/StyleBase.cs | 30 ++- src/Avalonia.Base/Styling/StyleCache.cs | 58 ---- src/Avalonia.Base/Styling/Styler.cs | 35 --- src/Avalonia.Base/Styling/Styles.cs | 47 ++-- src/Avalonia.Controls/Application.cs | 2 - src/Avalonia.Controls/TopLevel.cs | 3 +- .../Styling/StyleInclude.cs | 2 - .../Animation/AnimatableTests.cs | 2 +- .../Styling/StyleTests.cs | 194 ++++++-------- .../Styling/StyledElementTests.cs | 78 +++--- .../Styling/StyledElementTests_Theming.cs | 21 +- .../Styling/ResourceBenchmarks.cs | 1 - .../Themes/FluentBenchmark.cs | 1 - .../ContentControlTests.cs | 63 ++--- .../ListBoxTests.cs | 4 +- .../Primitives/SelectingItemsControlTests.cs | 12 + .../Primitives/TemplatedControlTests.cs | 249 +++++++++--------- .../TabControlTests.cs | 28 +- .../TreeViewTests.cs | 2 +- .../UserControlTests.cs | 25 +- .../Utils/HotKeyManagerTests.cs | 21 +- .../CompiledBindingExtensionTests.cs | 6 + tests/Avalonia.UnitTests/TestServices.cs | 14 +- .../Avalonia.UnitTests/UnitTestApplication.cs | 5 +- 30 files changed, 494 insertions(+), 636 deletions(-) delete mode 100644 src/Avalonia.Base/Styling/IStyler.cs delete mode 100644 src/Avalonia.Base/Styling/StyleCache.cs delete mode 100644 src/Avalonia.Base/Styling/Styler.cs diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 2f3f672d54..c72f398fd9 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -355,22 +355,19 @@ namespace Avalonia { if (_initCount == 0 && !_styled) { - var styler = AvaloniaLocator.Current.GetService(); var hasPromotedTheme = _hasPromotedTheme; - if (styler is object) - { - GetValueStore().BeginStyling(); + GetValueStore().BeginStyling(); - try - { - styler.ApplyStyles(this); - } - finally - { - _styled = true; - GetValueStore().EndStyling(); - } + try + { + ApplyControlTheme(); + ApplyStyles(this); + } + finally + { + _styled = true; + GetValueStore().EndStyling(); } if (hasPromotedTheme) @@ -509,31 +506,6 @@ namespace Avalonia }; } - ControlTheme? IStyleable.GetEffectiveTheme() - { - var theme = Theme; - - // Explitly set Theme property takes precedence. - if (theme is not null) - return theme; - - // If the Theme property is not set, try to find a ControlTheme resource with our StyleKey. - if (_implicitTheme is null) - { - var key = ((IStyleable)this).StyleKey; - - if (this.TryFindResource(key, out var value) && value is ControlTheme t) - _implicitTheme = t; - else - _implicitTheme = s_invalidTheme; - } - - if (_implicitTheme != s_invalidTheme) - return _implicitTheme; - - return null; - } - void IStyleable.DetachStyles() => DetachStyles(); void IStyleHost.StylesAdded(IReadOnlyList styles) @@ -670,6 +642,31 @@ namespace Avalonia { } + internal ControlTheme? GetEffectiveTheme() + { + var theme = Theme; + + // Explitly set Theme property takes precedence. + if (theme is not null) + return theme; + + // If the Theme property is not set, try to find a ControlTheme resource with our StyleKey. + if (_implicitTheme is null) + { + var key = ((IStyleable)this).StyleKey; + + if (this.TryFindResource(key, out var value) && value is ControlTheme t) + _implicitTheme = t; + else + _implicitTheme = s_invalidTheme; + } + + if (_implicitTheme != s_invalidTheme) + return _implicitTheme; + + return null; + } + private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted) { if (o is StyledElement element) @@ -734,6 +731,56 @@ namespace Avalonia } } + private void ApplyControlTheme() + { + var theme = GetEffectiveTheme(); + + if (theme is not null) + ApplyControlTheme(theme); + + if (TemplatedParent is StyledElement styleableParent && + styleableParent.GetEffectiveTheme() is { } parentTheme) + { + ApplyControlTheme(parentTheme); + } + } + + private void ApplyControlTheme(ControlTheme theme) + { + if (theme.BasedOn is ControlTheme basedOn) + ApplyControlTheme(basedOn); + + theme.TryAttach(this, null); + + if (theme.HasChildren) + { + foreach (var child in theme.Children) + ApplyStyle(child, null); + } + } + + private void ApplyStyles(IStyleHost host) + { + var parent = host.StylingParent; + if (parent != null) + ApplyStyles(parent); + + if (host.IsStylesInitialized) + { + foreach (var style in host.Styles) + ApplyStyle(style, host); + } + } + + private void ApplyStyle(IStyle style, IStyleHost? host) + { + if (style is Style s) + s.TryAttach(this, host); + + foreach (var child in style.Children) + ApplyStyle(child, host); + } + private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e) { if (this.GetLogicalParent() == null && !(this is ILogicalRoot)) diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs index 46a3267f70..2971703c95 100644 --- a/src/Avalonia.Base/Styling/ControlTheme.cs +++ b/src/Avalonia.Base/Styling/ControlTheme.cs @@ -29,34 +29,27 @@ namespace Avalonia.Styling /// public ControlTheme? BasedOn { get; set; } - public override SelectorMatchResult TryAttach(IStyleable target, object? host) + public override string ToString() => TargetType?.Name ?? "ControlTheme"; + + internal override void SetParent(StyleBase? parent) + { + throw new InvalidOperationException("ControlThemes cannot be added as a nested style."); + } + + internal override SelectorMatchResult TryAttach(IStyleable target, object? host) { _ = target ?? throw new ArgumentNullException(nameof(target)); if (TargetType is null) throw new InvalidOperationException("ControlTheme has no TargetType."); - var result = BasedOn?.TryAttach(target, host) ?? SelectorMatchResult.NeverThisType; - if (HasSettersOrAnimations && TargetType.IsAssignableFrom(target.StyleKey)) { Attach(target, null); - result = SelectorMatchResult.AlwaysThisType; + return SelectorMatchResult.AlwaysThisType; } - var childResult = TryAttachChildren(target, host); - - if (childResult > result) - result = childResult; - - return result; - } - - public override string ToString() => TargetType?.Name ?? "ControlTheme"; - - internal override void SetParent(StyleBase? parent) - { - throw new InvalidOperationException("ControlThemes cannot be added as a nested style."); + return SelectorMatchResult.NeverThisType; } } } diff --git a/src/Avalonia.Base/Styling/IStyle.cs b/src/Avalonia.Base/Styling/IStyle.cs index 417739fb28..2dbaf963ee 100644 --- a/src/Avalonia.Base/Styling/IStyle.cs +++ b/src/Avalonia.Base/Styling/IStyle.cs @@ -14,15 +14,5 @@ namespace Avalonia.Styling /// Gets a collection of child styles. /// IReadOnlyList Children { get; } - - /// - /// Attaches the style and any child styles to a control if the style's selector matches. - /// - /// The control to attach to. - /// The element that hosts the style. - /// - /// A describing how the style matches the control. - /// - SelectorMatchResult TryAttach(IStyleable target, object? host); } } diff --git a/src/Avalonia.Base/Styling/IStyleable.cs b/src/Avalonia.Base/Styling/IStyleable.cs index e94fc5c4e6..dcc3988280 100644 --- a/src/Avalonia.Base/Styling/IStyleable.cs +++ b/src/Avalonia.Base/Styling/IStyleable.cs @@ -25,11 +25,6 @@ namespace Avalonia.Styling /// ITemplatedControl? TemplatedParent { get; } - /// - /// Gets the effective theme for the control as used by the syling system. - /// - ControlTheme? GetEffectiveTheme(); - void DetachStyles(); } } diff --git a/src/Avalonia.Base/Styling/IStyler.cs b/src/Avalonia.Base/Styling/IStyler.cs deleted file mode 100644 index d6477d169e..0000000000 --- a/src/Avalonia.Base/Styling/IStyler.cs +++ /dev/null @@ -1,8 +0,0 @@ - -namespace Avalonia.Styling -{ - public interface IStyler - { - void ApplyStyles(IStyleable control); - } -} diff --git a/src/Avalonia.Base/Styling/Style.cs b/src/Avalonia.Base/Styling/Style.cs index 913c437bc4..aad91824d3 100644 --- a/src/Avalonia.Base/Styling/Style.cs +++ b/src/Avalonia.Base/Styling/Style.cs @@ -1,5 +1,4 @@ using System; -using Avalonia.PropertyStore; namespace Avalonia.Styling { @@ -35,35 +34,6 @@ namespace Avalonia.Styling set => _selector = ValidateSelector(value); } - public override SelectorMatchResult TryAttach(IStyleable target, object? host) - { - _ = target ?? throw new ArgumentNullException(nameof(target)); - - var result = SelectorMatchResult.NeverThisType; - - if (HasSettersOrAnimations) - { - var match = Selector?.Match(target, Parent, true) ?? - (target == host ? - SelectorMatch.AlwaysThisInstance : - SelectorMatch.NeverThisInstance); - - if (match.IsMatch) - { - Attach(target, match.Activator); - } - - result = match.Result; - } - - var childResult = TryAttachChildren(target, host); - - if (childResult > result) - result = childResult; - - return result; - } - /// /// Returns a string representation of the style. /// @@ -88,6 +58,30 @@ namespace Avalonia.Styling base.SetParent(parent); } + internal override SelectorMatchResult TryAttach(IStyleable target, object? host) + { + _ = target ?? throw new ArgumentNullException(nameof(target)); + + var result = SelectorMatchResult.NeverThisType; + + if (HasSettersOrAnimations) + { + var match = Selector?.Match(target, Parent, true) ?? + (target == host ? + SelectorMatch.AlwaysThisInstance : + SelectorMatch.NeverThisInstance); + + if (match.IsMatch) + { + Attach(target, match.Activator); + } + + result = match.Result; + } + + return result; + } + private static Selector? ValidateSelector(Selector? selector) { if (selector is TemplateSelector) diff --git a/src/Avalonia.Base/Styling/StyleBase.cs b/src/Avalonia.Base/Styling/StyleBase.cs index c914fbf8cc..dba80df2e5 100644 --- a/src/Avalonia.Base/Styling/StyleBase.cs +++ b/src/Avalonia.Base/Styling/StyleBase.cs @@ -18,7 +18,6 @@ namespace Avalonia.Styling private IResourceDictionary? _resources; private List? _setters; private List? _animations; - private StyleCache? _childCache; private StyleInstance? _sharedInstance; public IList Children => _children ??= new(this); @@ -67,6 +66,7 @@ namespace Avalonia.Styling bool IResourceNode.HasResources => _resources?.Count > 0; IReadOnlyList IStyle.Children => (IReadOnlyList?)_children ?? Array.Empty(); + internal bool HasChildren => _children?.Count > 0; internal bool HasSettersOrAnimations => _setters?.Count > 0 || _animations?.Count > 0; public void Add(ISetter setter) => Setters.Add(setter); @@ -74,14 +74,26 @@ namespace Avalonia.Styling public event EventHandler? OwnerChanged; - public abstract SelectorMatchResult TryAttach(IStyleable target, object? host); - public bool TryGetResource(object key, out object? result) { - result = null; - return _resources?.TryGetResource(key, out result) ?? false; + if (_resources is not null && _resources.TryGetResource(key, out result)) + return true; + + if (_children is not null) + { + for (var i = 0; i < _children.Count; ++i) + { + if (_children[i].TryGetResource(key, out result)) + return true; + } + } + + result= null; + return false; } + internal abstract SelectorMatchResult TryAttach(IStyleable target, object? host); + internal ValueFrame Attach(IStyleable target, IStyleActivator? activator) { if (target is not AvaloniaObject ao) @@ -124,14 +136,6 @@ namespace Avalonia.Styling return instance; } - internal SelectorMatchResult TryAttachChildren(IStyleable target, object? host) - { - if (_children is null || _children.Count == 0) - return SelectorMatchResult.NeverThisType; - _childCache ??= new StyleCache(); - return _childCache.TryAttach(_children, target, host); - } - internal virtual void SetParent(StyleBase? parent) => Parent = parent; void IResourceProvider.AddOwner(IResourceHost owner) diff --git a/src/Avalonia.Base/Styling/StyleCache.cs b/src/Avalonia.Base/Styling/StyleCache.cs deleted file mode 100644 index 81196f6a27..0000000000 --- a/src/Avalonia.Base/Styling/StyleCache.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Avalonia.Styling -{ - /// - /// Simple cache for improving performance of applying styles. - /// - /// - /// Maps to a list of styles that are known be be possible - /// matches. - /// - internal class StyleCache : Dictionary?> - { - public SelectorMatchResult TryAttach(IList styles, IStyleable target, object? host) - { - if (TryGetValue(target.StyleKey, out var cached)) - { - if (cached is object) - { - var result = SelectorMatchResult.NeverThisType; - - foreach (var style in cached) - { - var childResult = style.TryAttach(target, host); - if (childResult > result) - result = childResult; - } - - return result; - } - else - { - return SelectorMatchResult.NeverThisType; - } - } - else - { - List? matches = null; - - foreach (var child in styles) - { - if (child.TryAttach(target, host) != SelectorMatchResult.NeverThisType) - { - matches ??= new List(); - matches.Add(child); - } - } - - Add(target.StyleKey, matches); - - return matches is null ? - SelectorMatchResult.NeverThisType : - SelectorMatchResult.AlwaysThisType; - } - } - } -} diff --git a/src/Avalonia.Base/Styling/Styler.cs b/src/Avalonia.Base/Styling/Styler.cs deleted file mode 100644 index ad5c1cd102..0000000000 --- a/src/Avalonia.Base/Styling/Styler.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; - -namespace Avalonia.Styling -{ - public class Styler : IStyler - { - public void ApplyStyles(IStyleable target) - { - _ = target ?? throw new ArgumentNullException(nameof(target)); - - // Apply the control theme. - target.GetEffectiveTheme()?.TryAttach(target, target); - - // If the control has a themed templated parent then apply the styles from the - // templated parent theme. - if (target.TemplatedParent is IStyleable styleableParent) - styleableParent.GetEffectiveTheme()?.TryAttach(target, styleableParent); - - // Apply styles from the rest of the tree. - if (target is IStyleHost styleHost) - ApplyStyles(target, styleHost); - } - - private void ApplyStyles(IStyleable target, IStyleHost host) - { - var parent = host.StylingParent; - - if (parent != null) - ApplyStyles(target, parent); - - if (host.IsStylesInitialized) - host.Styles.TryAttach(target, host); - } - } -} diff --git a/src/Avalonia.Base/Styling/Styles.cs b/src/Avalonia.Base/Styling/Styles.cs index c213475bb7..76271b9748 100644 --- a/src/Avalonia.Base/Styling/Styles.cs +++ b/src/Avalonia.Base/Styling/Styles.cs @@ -5,8 +5,6 @@ using System.Collections.Specialized; using Avalonia.Collections; using Avalonia.Controls; -#nullable enable - namespace Avalonia.Styling { /// @@ -20,7 +18,6 @@ namespace Avalonia.Styling private readonly AvaloniaList _styles = new(); private IResourceHost? _owner; private IResourceDictionary? _resources; - private StyleCache? _cache; public Styles() { @@ -116,12 +113,6 @@ namespace Avalonia.Styling set => _styles[index] = value; } - public SelectorMatchResult TryAttach(IStyleable target, object? host) - { - _cache ??= new StyleCache(); - return _cache.TryAttach(this, target, host); - } - /// public bool TryGetResource(object key, out object? value) { @@ -234,6 +225,22 @@ namespace Avalonia.Styling } } + internal SelectorMatchResult TryAttach(IStyleable target, object? host) + { + var result = SelectorMatchResult.NeverThisType; + + foreach (var s in this) + { + if (s is not Style style) + continue; + var r = style.TryAttach(target, host); + if (r > result) + result = r; + } + + return result; + } + private static IReadOnlyList ToReadOnlyList(ICollection list) { if (list is IReadOnlyList readOnlyList) @@ -246,7 +253,7 @@ namespace Avalonia.Styling return result; } - private static void InternalAdd(IList items, IResourceHost? owner, ref StyleCache? cache) + private static void InternalAdd(IList items, IResourceHost? owner) { if (owner is not null) { @@ -260,14 +267,9 @@ namespace Avalonia.Styling (owner as IStyleHost)?.StylesAdded(ToReadOnlyList(items)); } - - if (items.Count > 0) - { - cache = null; - } } - private static void InternalRemove(IList items, IResourceHost? owner, ref StyleCache? cache) + private static void InternalRemove(IList items, IResourceHost? owner) { if (owner is not null) { @@ -281,11 +283,6 @@ namespace Avalonia.Styling (owner as IStyleHost)?.StylesRemoved(ToReadOnlyList(items)); } - - if (items.Count > 0) - { - cache = null; - } } private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) @@ -300,14 +297,14 @@ namespace Avalonia.Styling switch (e.Action) { case NotifyCollectionChangedAction.Add: - InternalAdd(e.NewItems!, currentOwner, ref _cache); + InternalAdd(e.NewItems!, currentOwner); break; case NotifyCollectionChangedAction.Remove: - InternalRemove(e.OldItems!, currentOwner, ref _cache); + InternalRemove(e.OldItems!, currentOwner); break; case NotifyCollectionChangedAction.Replace: - InternalRemove(e.OldItems!, currentOwner, ref _cache); - InternalAdd(e.NewItems!, currentOwner, ref _cache); + InternalRemove(e.OldItems!, currentOwner); + InternalAdd(e.NewItems!, currentOwner); break; } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 69fd6cabf8..dc9a0207ad 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -39,7 +39,6 @@ namespace Avalonia private readonly Lazy _clipboard = new Lazy(() => (IClipboard?)AvaloniaLocator.Current.GetService(typeof(IClipboard))); - private readonly Styler _styler = new Styler(); private Styles? _styles; private IResourceDictionary? _resources; private bool _notifyingResourcesChanged; @@ -232,7 +231,6 @@ namespace Avalonia .Bind().ToConstant(FocusManager) .Bind().ToConstant(InputManager) .Bind().ToTransient() - .Bind().ToConstant(_styler) .Bind().ToConstant(AvaloniaScheduler.Instance) .Bind().ToConstant(DragDropDevice.Instance); diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 6804c9ecb9..515535bf39 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -145,7 +145,6 @@ namespace Avalonia.Controls _actualTransparencyLevel = PlatformImpl.TransparencyLevel; dependencyResolver = dependencyResolver ?? AvaloniaLocator.Current; - var styler = TryGetService(dependencyResolver); _accessKeyHandler = TryGetService(dependencyResolver); _inputManager = TryGetService(dependencyResolver); @@ -183,7 +182,7 @@ namespace Avalonia.Controls _globalStyles.GlobalStylesRemoved += ((IStyleHost)this).StylesRemoved; } - styler?.ApplyStyles(this); + ApplyStyling(); ClientSize = impl.ClientSize; FrameSize = impl.FrameSize; diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index b1725245bb..8af49b5480 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -82,8 +82,6 @@ namespace Avalonia.Markup.Xaml.Styling } } - public SelectorMatchResult TryAttach(IStyleable target, object? host) => Loaded.TryAttach(target, host); - public bool TryGetResource(object key, out object? value) { if (!_isLoading) diff --git a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs index 19ae0dc260..668b8a875c 100644 --- a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs +++ b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs @@ -498,7 +498,7 @@ namespace Avalonia.Base.UnitTests.Animation private static IDisposable Start() { var clock = new MockGlobalClock(); - var services = TestServices.RealStyler.With(globalClock: clock); + var services = new TestServices(globalClock: clock); return UnitTestApplication.Start(services); } diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs b/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs index b7455f9b3f..e7ecba61a7 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs @@ -5,7 +5,6 @@ using Avalonia.Base.UnitTests.Animation; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Data; -using Avalonia.Diagnostics; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -301,8 +300,6 @@ namespace Avalonia.Base.UnitTests.Styling [Fact] public void Inactive_Values_Should_Not_Be_Made_Active_During_Style_Attach() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); - var root = new TestRoot { Styles = @@ -337,8 +334,6 @@ namespace Avalonia.Base.UnitTests.Styling [Fact] public void Inactive_Bindings_Should_Not_Be_Made_Active_During_Style_Attach() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); - var root = new TestRoot { Styles = @@ -380,8 +375,6 @@ namespace Avalonia.Base.UnitTests.Styling [Fact] public void Inactive_Values_Should_Not_Be_Made_Active_During_Style_Detach() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); - var root = new TestRoot { Styles = @@ -417,8 +410,6 @@ namespace Avalonia.Base.UnitTests.Styling [Fact] public void Inactive_Values_Should_Not_Be_Made_Active_During_Style_Detach_2() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); - var root = new TestRoot { Styles = @@ -454,8 +445,6 @@ namespace Avalonia.Base.UnitTests.Styling [Fact] public void Inactive_Bindings_Should_Not_Be_Made_Active_During_Style_Detach() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); - var root = new TestRoot { Styles = @@ -598,41 +587,36 @@ namespace Avalonia.Base.UnitTests.Styling [Fact] public void Removing_Style_Should_Detach_From_Control() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + var border = new Border(); + var root = new TestRoot { - var border = new Border(); - var root = new TestRoot - { - Styles = - { - new Style(x => x.OfType()) + Styles = + { + new Style(x => x.OfType()) + { + Setters = { - Setters = - { - new Setter(Border.BorderThicknessProperty, new Thickness(4)), - } + new Setter(Border.BorderThicknessProperty, new Thickness(4)), } - }, - Child = border, - }; + } + }, + Child = border, + }; - root.Measure(Size.Infinity); - Assert.Equal(new Thickness(4), border.BorderThickness); + root.Measure(Size.Infinity); + Assert.Equal(new Thickness(4), border.BorderThickness); - root.Styles.RemoveAt(0); - Assert.Equal(new Thickness(0), border.BorderThickness); - } + root.Styles.RemoveAt(0); + Assert.Equal(new Thickness(0), border.BorderThickness); } [Fact] public void Adding_Style_Should_Attach_To_Control() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + var border = new Border(); + var root = new TestRoot { - var border = new Border(); - var root = new TestRoot - { - Styles = + Styles = { new Style(x => x.OfType()) { @@ -642,34 +626,31 @@ namespace Avalonia.Base.UnitTests.Styling } } }, - Child = border, - }; + Child = border, + }; - root.Measure(Size.Infinity); - Assert.Equal(new Thickness(4), border.BorderThickness); + root.Measure(Size.Infinity); + Assert.Equal(new Thickness(4), border.BorderThickness); - root.Styles.Add(new Style(x => x.OfType()) - { - Setters = + root.Styles.Add(new Style(x => x.OfType()) + { + Setters = { new Setter(Border.BorderThicknessProperty, new Thickness(6)), } - }); + }); - root.Measure(Size.Infinity); - Assert.Equal(new Thickness(6), border.BorderThickness); - } + root.Measure(Size.Infinity); + Assert.Equal(new Thickness(6), border.BorderThickness); } [Fact] public void Removing_Style_With_Nested_Style_Should_Detach_From_Control() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + var border = new Border(); + var root = new TestRoot { - var border = new Border(); - var root = new TestRoot - { - Styles = + Styles = { new Styles { @@ -682,96 +663,89 @@ namespace Avalonia.Base.UnitTests.Styling } } }, - Child = border, - }; + Child = border, + }; - root.Measure(Size.Infinity); - Assert.Equal(new Thickness(4), border.BorderThickness); + root.Measure(Size.Infinity); + Assert.Equal(new Thickness(4), border.BorderThickness); - root.Styles.RemoveAt(0); - Assert.Equal(new Thickness(0), border.BorderThickness); - } + root.Styles.RemoveAt(0); + Assert.Equal(new Thickness(0), border.BorderThickness); } - + [Fact] public void Adding_Nested_Style_Should_Attach_To_Control() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + var border = new Border(); + var root = new TestRoot { - var border = new Border(); - var root = new TestRoot + Styles = { - Styles = + new Styles { - new Styles + new Style(x => x.OfType()) { - new Style(x => x.OfType()) + Setters = { - Setters = - { - new Setter(Border.BorderThicknessProperty, new Thickness(4)), - } + new Setter(Border.BorderThicknessProperty, new Thickness(4)), } } - }, - Child = border, - }; + } + }, + Child = border, + }; - root.Measure(Size.Infinity); - Assert.Equal(new Thickness(4), border.BorderThickness); + root.Measure(Size.Infinity); + Assert.Equal(new Thickness(4), border.BorderThickness); - ((Styles)root.Styles[0]).Add(new Style(x => x.OfType()) + ((Styles)root.Styles[0]).Add(new Style(x => x.OfType()) + { + Setters = { - Setters = - { - new Setter(Border.BorderThicknessProperty, new Thickness(6)), - } - }); + new Setter(Border.BorderThicknessProperty, new Thickness(6)), + } + }); - root.Measure(Size.Infinity); - Assert.Equal(new Thickness(6), border.BorderThickness); - } + root.Measure(Size.Infinity); + Assert.Equal(new Thickness(6), border.BorderThickness); } [Fact] public void Removing_Nested_Style_Should_Detach_From_Control() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + var border = new Border(); + var root = new TestRoot { - var border = new Border(); - var root = new TestRoot + Styles = { - Styles = + new Styles { - new Styles + new Style(x => x.OfType()) { - new Style(x => x.OfType()) + Setters = { - Setters = - { - new Setter(Border.BorderThicknessProperty, new Thickness(4)), - } - }, - new Style(x => x.OfType()) + new Setter(Border.BorderThicknessProperty, new Thickness(4)), + } + }, + new Style(x => x.OfType()) + { + Setters = { - Setters = - { - new Setter(Border.BorderThicknessProperty, new Thickness(6)), - } - }, - } - }, - Child = border, - }; + new Setter(Border.BorderThicknessProperty, new Thickness(6)), + } + }, + } + }, + Child = border, + }; - root.Measure(Size.Infinity); - Assert.Equal(new Thickness(6), border.BorderThickness); + root.Measure(Size.Infinity); + Assert.Equal(new Thickness(6), border.BorderThickness); - ((Styles)root.Styles[0]).RemoveAt(1); + ((Styles)root.Styles[0]).RemoveAt(1); - root.Measure(Size.Infinity); - Assert.Equal(new Thickness(4), border.BorderThickness); - } + root.Measure(Size.Infinity); + Assert.Equal(new Thickness(4), border.BorderThickness); } [Fact] diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests.cs b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests.cs index c01e22347b..65fe50b545 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests.cs @@ -249,65 +249,67 @@ namespace Avalonia.Base.UnitTests.Styling } [Fact] - public void Adding_Tree_To_IStyleRoot_Should_Style_Controls() + public void Adding_Tree_To_Root_Should_Style_Controls() { - using (AvaloniaLocator.EnterScope()) + var root = new TestRoot { - var root = new TestRoot(); - var parent = new Border(); - var child = new Border(); - var grandchild = new Control(); - var styler = new Mock(); - - AvaloniaLocator.CurrentMutable.Bind().ToConstant(styler.Object); + Styles = + { + new Style(x => x.Is()) + { + Setters = { new Setter(Control.TagProperty, "foo") } + } + } + }; - parent.Child = child; - child.Child = grandchild; + var grandchild = new Control(); + var child = new Border { Child = grandchild }; + var parent = new Border { Child = child }; - styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Never()); + Assert.Null(parent.Tag); + Assert.Null(child.Tag); + Assert.Null(grandchild.Tag); - root.Child = parent; + root.Child = parent; - styler.Verify(x => x.ApplyStyles(parent), Times.Once()); - styler.Verify(x => x.ApplyStyles(child), Times.Once()); - styler.Verify(x => x.ApplyStyles(grandchild), Times.Once()); - } + Assert.Equal("foo", parent.Tag); + Assert.Equal("foo", child.Tag); + Assert.Equal("foo", grandchild.Tag); } [Fact] public void Styles_Not_Applied_Until_Initialization_Finished() { - using (AvaloniaLocator.EnterScope()) + var root = new TestRoot { - var root = new TestRoot(); - var child = new Border(); - var styler = new Mock(); + Styles = + { + new Style(x => x.Is()) + { + Setters = { new Setter(Control.TagProperty, "foo") } + } + } + }; - AvaloniaLocator.CurrentMutable.Bind().ToConstant(styler.Object); + var child = new Border(); - ((ISupportInitialize)child).BeginInit(); - root.Child = child; - styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Never()); + ((ISupportInitialize)child).BeginInit(); + root.Child = child; + Assert.Null(child.Tag); - ((ISupportInitialize)child).EndInit(); - styler.Verify(x => x.ApplyStyles(child), Times.Once()); - } + ((ISupportInitialize)child).EndInit(); + Assert.Equal("foo", child.Tag); } [Fact] public void Name_Cannot_Be_Set_After_Added_To_Logical_Tree() { - using (AvaloniaLocator.EnterScope()) - { - var root = new TestRoot(); - var child = new Border(); - - AvaloniaLocator.CurrentMutable.BindToSelf(new Styler()); + var root = new TestRoot(); + var child = new Border(); - root.Child = child; + root.Child = child; - Assert.Throws(() => child.Name = "foo"); - } + Assert.Throws(() => child.Name = "foo"); } [Fact] @@ -328,7 +330,7 @@ namespace Avalonia.Base.UnitTests.Styling [Fact] public void Style_Is_Removed_When_Control_Removed_From_Logical_Tree() { - var app = UnitTestApplication.Start(TestServices.RealStyler); + var app = UnitTestApplication.Start(); var target = new Border(); var root = new TestRoot { diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs index 45188de6cb..a1dac931ce 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs @@ -19,7 +19,6 @@ public class StyledElementTests_Theming [Fact] public void Theme_Is_Applied_When_Attached_To_Logical_Tree() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); var target = CreateTarget(); Assert.Null(target.Template); @@ -37,7 +36,6 @@ public class StyledElementTests_Theming [Fact] public void Theme_Is_Applied_To_Derived_Class_When_Attached_To_Logical_Tree() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); var target = new DerivedThemedControl { Theme = CreateTheme(), @@ -58,7 +56,6 @@ public class StyledElementTests_Theming [Fact] public void Theme_Is_Detached_When_Theme_Property_Cleared() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); var target = CreateTarget(); var root = CreateRoot(target); @@ -71,8 +68,6 @@ public class StyledElementTests_Theming [Fact] public void Theme_Is_Detached_From_Template_Controls_When_Theme_Property_Cleared() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); - var theme = new ControlTheme { TargetType = typeof(ThemedControl), @@ -105,7 +100,6 @@ public class StyledElementTests_Theming [Fact] public void Theme_Is_Applied_On_Layout_After_Theme_Property_Changes() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); var target = new ThemedControl(); var root = CreateRoot(target); @@ -124,7 +118,6 @@ public class StyledElementTests_Theming [Fact] public void BasedOn_Theme_Is_Applied_When_Attached_To_Logical_Tree() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); var target = CreateTarget(CreateDerivedTheme()); Assert.Null(target.Template); @@ -163,7 +156,6 @@ public class StyledElementTests_Theming [Fact] public void Implicit_Theme_Is_Applied_When_Attached_To_Logical_Tree() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); var target = CreateTarget(); var root = CreateRoot(target); Assert.NotNull(target.Template); @@ -178,21 +170,19 @@ public class StyledElementTests_Theming [Fact] public void Implicit_Theme_Is_Cleared_When_Removed_From_Logical_Tree() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); var target = CreateTarget(); var root = CreateRoot(target); - Assert.NotNull(((IStyleable)target).GetEffectiveTheme()); + Assert.NotNull(target.GetEffectiveTheme()); root.Child = null; - Assert.Null(((IStyleable)target).GetEffectiveTheme()); + Assert.Null(target.GetEffectiveTheme()); } [Fact] public void Nested_Style_Can_Override_Property_In_Inner_Templated_Control() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); var target = new ThemedControl2 { Theme = new ControlTheme(typeof(ThemedControl2)) @@ -236,7 +226,6 @@ public class StyledElementTests_Theming [Fact] public void Theme_Is_Applied_When_Attached_To_Logical_Tree() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); var target = CreateTarget(); Assert.Null(target.Theme); @@ -248,16 +237,15 @@ public class StyledElementTests_Theming Assert.NotNull(target.Template); var border = Assert.IsType(target.VisualChild); - Assert.Equal(border.Background, Brushes.Red); + Assert.Equal(Brushes.Red, border.Background); target.Classes.Add("foo"); - Assert.Equal(border.Background, Brushes.Green); + Assert.Equal(Brushes.Green, border.Background); } [Fact] public void Theme_Can_Be_Changed_By_Style_Class() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); var target = CreateTarget(); var theme1 = CreateTheme(); var theme2 = new ControlTheme(typeof(ThemedControl)); @@ -290,7 +278,6 @@ public class StyledElementTests_Theming [Fact] public void Theme_Can_Be_Set_To_LocalValue_While_Updating_Due_To_Style_Class() { - using var app = UnitTestApplication.Start(TestServices.RealStyler); var target = CreateTarget(); var theme1 = CreateTheme(); var theme2 = new ControlTheme(typeof(ThemedControl)); diff --git a/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs b/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs index b16e891924..59953f457a 100644 --- a/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs +++ b/tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs @@ -22,7 +22,6 @@ namespace Avalonia.Benchmarks.Styling platform: new AppBuilder().RuntimePlatform, renderInterface: new MockPlatformRenderInterface(), standardCursorFactory: Mock.Of(), - styler: new Styler(), theme: () => CreateTheme(), threadingInterface: new NullThreadingPlatform(), fontManagerImpl: new MockFontManagerImpl(), diff --git a/tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs b/tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs index ae874b8a61..1115bc9760 100644 --- a/tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs @@ -46,7 +46,6 @@ namespace Avalonia.Benchmarks.Themes platform: new AppBuilder().RuntimePlatform, renderInterface: new MockPlatformRenderInterface(), standardCursorFactory: Mock.Of(), - styler: new Styler(), theme: () => LoadFluentTheme(), threadingInterface: new NullThreadingPlatform(), fontManagerImpl: new MockFontManagerImpl(), diff --git a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs index 7cc0bae97f..1e37093736 100644 --- a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs @@ -37,11 +37,19 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Templated_Children_Should_Be_Styled() { - var root = new TestRoot(); + var root = new TestRoot + { + Styles = + { + new Style(x => x.Is()) + { + Setters = { new Setter(Control.TagProperty, "foo") } + } + } + }; + var target = new ContentControl(); - var styler = new Mock(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(styler.Object); target.Content = "Foo"; target.Template = GetTemplate(); root.Child = target; @@ -49,10 +57,8 @@ namespace Avalonia.Controls.UnitTests target.ApplyTemplate(); target.Presenter.ApplyTemplate(); - styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); - styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); - styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); - styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + foreach (Control child in target.GetTemplateChildren()) + Assert.Equal("foo", child.Tag); } [Fact] @@ -332,40 +338,37 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Should_Set_Child_LogicalParent_After_Removing_And_Adding_Back_To_Logical_Tree() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + var target = new ContentControl(); + var root = new TestRoot { - var target = new ContentControl(); - var root = new TestRoot + Styles = { - Styles = + new Style(x => x.OfType()) { - new Style(x => x.OfType()) + Setters = { - Setters = - { - new Setter(ContentControl.TemplateProperty, GetTemplate()), - } + new Setter(ContentControl.TemplateProperty, GetTemplate()), } - }, - Child = target - }; + } + }, + Child = target + }; - target.Content = "Foo"; - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); + target.Content = "Foo"; + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); - Assert.Equal(target, target.Presenter.Child.LogicalParent); + Assert.Equal(target, target.Presenter.Child.LogicalParent); - root.Child = null; + root.Child = null; - Assert.Null(target.Template); + Assert.Null(target.Template); - target.Content = null; - root.Child = target; - target.Content = "Bar"; + target.Content = null; + root.Child = target; + target.Content = "Bar"; - Assert.Equal(target, target.Presenter.Child.LogicalParent); - } + Assert.Equal(target, target.Presenter.Child.LogicalParent); } private FuncControlTemplate GetTemplate() diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index f6d96edb99..7b7f0fcc98 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -99,7 +99,7 @@ namespace Avalonia.Controls.UnitTests using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) { var items = new[] { "Foo", "Bar", "Baz " }; - var theme = new ControlTheme(); + var theme = new ControlTheme(typeof(ListBoxItem)); var target = new ListBox { Template = ListBoxTemplate(), @@ -121,7 +121,7 @@ namespace Avalonia.Controls.UnitTests using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) { var items = new[] { "Foo", "Bar", "Baz " }; - var theme = new ControlTheme(); + var theme = new ControlTheme(typeof(ListBoxItem)); var target = new ListBox { Template = ListBoxTemplate(), diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 330cbfd7b9..100e813326 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -998,6 +998,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Order_Of_Setting_Items_And_SelectedIndex_During_Initialization_Should_Not_Matter() { + using var app = Start(); var items = new[] { "Foo", "Bar" }; var target = new SelectingItemsControl(); @@ -1015,6 +1016,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Order_Of_Setting_Items_And_SelectedItem_During_Initialization_Should_Not_Matter() { + using var app = Start(); var items = new[] { "Foo", "Bar" }; var target = new SelectingItemsControl(); @@ -1847,6 +1849,7 @@ namespace Avalonia.Controls.UnitTests.Primitives public void Preserves_SelectedItem_When_Items_Changed() { // Issue #4048 + using var app = Start(); var target = new SelectingItemsControl { Items = new[] { "foo", "bar", "baz"}, @@ -1867,6 +1870,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Setting_SelectedItems_Raises_PropertyChanged() { + using var app = Start(); var target = new TestSelector { Items = new[] { "foo", "bar", "baz" }, @@ -1895,6 +1899,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Setting_Selection_Raises_SelectedItems_PropertyChanged() { + using var app = Start(); var target = new TestSelector { Items = new[] { "foo", "bar", "baz" }, @@ -2050,6 +2055,13 @@ namespace Avalonia.Controls.UnitTests.Primitives } } + private static IDisposable Start() + { + return UnitTestApplication.Start(new TestServices( + fontManagerImpl: new MockFontManagerImpl(), + textShaperImpl: new MockTextShaperImpl())); + } + private static void Prepare(SelectingItemsControl target) { var root = new TestRoot diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs index 58d2eedb1f..a7609a6704 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs @@ -170,36 +170,41 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Templated_Children_Should_Be_Styled() { - using (UnitTestApplication.Start(TestServices.MockStyler)) - { - TestTemplatedControl target; + TestTemplatedControl target; - var root = new TestRoot + var root = new TestRoot + { + Styles = { - Child = target = new TestTemplatedControl + new Style(x => x.Is()) { - Template = new FuncControlTemplate((_, __) => + Setters = { - return new StackPanel - { - Children = + new Setter(Control.TagProperty, "foo") + } + } + }, + Child = target = new TestTemplatedControl + { + Template = new FuncControlTemplate((_, __) => + { + return new StackPanel + { + Children = { new TextBlock { } } - }; - }), - } - }; + }; + }), + } + }; - target.ApplyTemplate(); + target.ApplyTemplate(); - var styler = Mock.Get(UnitTestApplication.Current.Services.Styler); - styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); - styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); - styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); - } + foreach (Control child in target.GetTemplateChildren()) + Assert.Equal("foo", child.Tag); } [Fact] @@ -351,166 +356,154 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Removing_From_LogicalTree_Should_Not_Remove_Child() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + Border templateChild = new Border(); + TestTemplatedControl target; + var root = new TestRoot { - Border templateChild = new Border(); - TestTemplatedControl target; - var root = new TestRoot + Styles = { - Styles = + new Style(x => x.OfType()) { - new Style(x => x.OfType()) + Setters = { - Setters = - { - new Setter( - TemplatedControl.TemplateProperty, - new FuncControlTemplate((_, __) => new Decorator - { - Child = new Border(), - })) - } + new Setter( + TemplatedControl.TemplateProperty, + new FuncControlTemplate((_, __) => new Decorator + { + Child = new Border(), + })) } - }, - Child = target = new TestTemplatedControl() - }; + } + }, + Child = target = new TestTemplatedControl() + }; - Assert.NotNull(target.Template); - target.ApplyTemplate(); + Assert.NotNull(target.Template); + target.ApplyTemplate(); - root.Child = null; + root.Child = null; - Assert.Null(target.Template); - Assert.IsType(target.GetVisualChildren().Single()); - } + Assert.Null(target.Template); + Assert.IsType(target.GetVisualChildren().Single()); } [Fact] public void Re_adding_To_Same_LogicalTree_Should_Not_Recreate_Template() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + TestTemplatedControl target; + var root = new TestRoot { - TestTemplatedControl target; - var root = new TestRoot + Styles = { - Styles = + new Style(x => x.OfType()) { - new Style(x => x.OfType()) + Setters = { - Setters = - { - new Setter( - TemplatedControl.TemplateProperty, - new FuncControlTemplate((_, __) => new Decorator - { - Child = new Border(), - })) - } + new Setter( + TemplatedControl.TemplateProperty, + new FuncControlTemplate((_, __) => new Decorator + { + Child = new Border(), + })) } - }, - Child = target = new TestTemplatedControl() - }; + } + }, + Child = target = new TestTemplatedControl() + }; - Assert.NotNull(target.Template); - target.ApplyTemplate(); - var expected = (Decorator)target.GetVisualChildren().Single(); + Assert.NotNull(target.Template); + target.ApplyTemplate(); + var expected = (Decorator)target.GetVisualChildren().Single(); - root.Child = null; - root.Child = target; - target.ApplyTemplate(); + root.Child = null; + root.Child = target; + target.ApplyTemplate(); - Assert.Same(expected, target.GetVisualChildren().Single()); - } + Assert.Same(expected, target.GetVisualChildren().Single()); } [Fact] public void Re_adding_To_Different_LogicalTree_Should_Recreate_Template() { - using (UnitTestApplication.Start(TestServices.RealStyler)) - { - TestTemplatedControl target; + TestTemplatedControl target; - var root = new TestRoot + var root = new TestRoot + { + Styles = { - Styles = + new Style(x => x.OfType()) { - new Style(x => x.OfType()) + Setters = { - Setters = - { - new Setter( - TemplatedControl.TemplateProperty, - new FuncControlTemplate((_, __) => new Decorator - { - Child = new Border(), - })) - } + new Setter( + TemplatedControl.TemplateProperty, + new FuncControlTemplate((_, __) => new Decorator + { + Child = new Border(), + })) } - }, - Child = target = new TestTemplatedControl() - }; + } + }, + Child = target = new TestTemplatedControl() + }; - var root2 = new TestRoot + var root2 = new TestRoot + { + Styles = { - Styles = + new Style(x => x.OfType()) { - new Style(x => x.OfType()) + Setters = { - Setters = - { - new Setter( - TemplatedControl.TemplateProperty, - new FuncControlTemplate((_, __) => new Decorator - { - Child = new Border(), - })) - } + new Setter( + TemplatedControl.TemplateProperty, + new FuncControlTemplate((_, __) => new Decorator + { + Child = new Border(), + })) } - }, - }; + } + }, + }; - Assert.NotNull(target.Template); - target.ApplyTemplate(); + Assert.NotNull(target.Template); + target.ApplyTemplate(); - var expected = (Decorator)target.GetVisualChildren().Single(); + var expected = (Decorator)target.GetVisualChildren().Single(); - root.Child = null; - root2.Child = target; - target.ApplyTemplate(); + root.Child = null; + root2.Child = target; + target.ApplyTemplate(); - var child = target.GetVisualChildren().Single(); - Assert.NotNull(target.Template); - Assert.NotNull(child); - Assert.NotSame(expected, child); - } + var child = target.GetVisualChildren().Single(); + Assert.NotNull(target.Template); + Assert.NotNull(child); + Assert.NotSame(expected, child); } [Fact] public void Moving_To_New_LogicalTree_Should_Detach_Attach_Template_Child() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + TestTemplatedControl target; + var root = new TestRoot { - TestTemplatedControl target; - var root = new TestRoot + Child = target = new TestTemplatedControl { - Child = target = new TestTemplatedControl - { - Template = new FuncControlTemplate((_, __) => new Decorator()), - } - }; + Template = new FuncControlTemplate((_, __) => new Decorator()), + } + }; - Assert.NotNull(target.Template); - target.ApplyTemplate(); + Assert.NotNull(target.Template); + target.ApplyTemplate(); - var templateChild = (ILogical)target.GetVisualChildren().Single(); - Assert.True(templateChild.IsAttachedToLogicalTree); + var templateChild = (ILogical)target.GetVisualChildren().Single(); + Assert.True(templateChild.IsAttachedToLogicalTree); - root.Child = null; - Assert.False(templateChild.IsAttachedToLogicalTree); + root.Child = null; + Assert.False(templateChild.IsAttachedToLogicalTree); - var newRoot = new TestRoot { Child = target }; - Assert.True(templateChild.IsAttachedToLogicalTree); - } + var newRoot = new TestRoot { Child = target }; + Assert.True(templateChild.IsAttachedToLogicalTree); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index c54d7efe61..d8a897d600 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -227,28 +227,24 @@ namespace Avalonia.Controls.UnitTests }; var template = new FuncControlTemplate((x, __) => new Decorator()); - - using (UnitTestApplication.Start(TestServices.RealStyler)) + var root = new TestRoot { - var root = new TestRoot + Styles = { - Styles = + new Style(x => x.OfType()) { - new Style(x => x.OfType()) + Setters = { - Setters = - { - new Setter(TemplatedControl.TemplateProperty, template) - } + new Setter(TemplatedControl.TemplateProperty, template) } - }, - Child = new TabControl - { - Template = TabControlTemplate(), - Items = collection, } - }; - } + }, + Child = new TabControl + { + Template = TabControlTemplate(), + Items = collection, + } + }; Assert.Same(collection[0].Template, template); Assert.Same(collection[1].Template, template); diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 9cf21423a3..9ae6fd98b5 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -77,7 +77,7 @@ namespace Avalonia.Controls.UnitTests public void Items_Should_Be_Created_Using_ItemConatinerTheme_If_Present() { TreeView target; - var theme = new ControlTheme(); + var theme = new ControlTheme(typeof(TreeViewItem)); var root = new TestRoot { diff --git a/tests/Avalonia.Controls.UnitTests/UserControlTests.cs b/tests/Avalonia.Controls.UnitTests/UserControlTests.cs index 3d1980b0eb..6342d9d21f 100644 --- a/tests/Avalonia.Controls.UnitTests/UserControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/UserControlTests.cs @@ -13,26 +13,23 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Should_Be_Styled_As_UserControl() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + var target = new UserControl(); + var root = new TestRoot { - var target = new UserControl(); - var root = new TestRoot + Styles = { - Styles = + new Style(x => x.OfType()) { - new Style(x => x.OfType()) + Setters = { - Setters = - { - new Setter(TemplatedControl.TemplateProperty, GetTemplate()) - } + new Setter(TemplatedControl.TemplateProperty, GetTemplate()) } - }, - Child = target, - }; + } + }, + Child = target, + }; - Assert.NotNull(target.Template); - } + Assert.NotNull(target.Template); } private FuncControlTemplate GetTemplate() diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs index e4d177f7ca..3202924a9d 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -23,11 +23,8 @@ namespace Avalonia.Controls.UnitTests.Utils { using (AvaloniaLocator.EnterScope()) { - var styler = new Mock(); - AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()) - .Bind().ToConstant(styler.Object); + .Bind().ToConstant(new WindowingPlatformMock()); var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control); var gesture2 = new KeyGesture(Key.B, KeyModifiers.Control); @@ -67,13 +64,11 @@ namespace Avalonia.Controls.UnitTests.Utils { using (AvaloniaLocator.EnterScope()) { - var styler = new Mock(); var target = new KeyboardDevice(); var commandResult = 0; var expectedParameter = 1; AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()) - .Bind().ToConstant(styler.Object); + .Bind().ToConstant(new WindowingPlatformMock()); var gesture = new KeyGesture(Key.A, KeyModifiers.Control); @@ -112,12 +107,10 @@ namespace Avalonia.Controls.UnitTests.Utils { using (AvaloniaLocator.EnterScope()) { - var styler = new Mock(); var target = new KeyboardDevice(); var isExecuted = false; AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()) - .Bind().ToConstant(styler.Object); + .Bind().ToConstant(new WindowingPlatformMock()); var gesture = new KeyGesture(Key.A, KeyModifiers.Control); @@ -154,12 +147,10 @@ namespace Avalonia.Controls.UnitTests.Utils { using (AvaloniaLocator.EnterScope()) { - var styler = new Mock(); var target = new KeyboardDevice(); var clickExecutedCount = 0; AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()) - .Bind().ToConstant(styler.Object); + .Bind().ToConstant(new WindowingPlatformMock()); var gesture = new KeyGesture(Key.A, KeyModifiers.Control); @@ -208,13 +199,11 @@ namespace Avalonia.Controls.UnitTests.Utils { using (AvaloniaLocator.EnterScope()) { - var styler = new Mock(); var target = new KeyboardDevice(); var clickExecutedCount = 0; var commandExecutedCount = 0; AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()) - .Bind().ToConstant(styler.Object); + .Bind().ToConstant(new WindowingPlatformMock()); var gesture = new KeyGesture(Key.A, KeyModifiers.Control); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 2d7dcf4b45..88d2cc2912 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -5,6 +5,7 @@ using System.ComponentModel; using System.Globalization; using System.Linq; using System.Reactive.Subjects; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Presenters; @@ -26,6 +27,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions { public class CompiledBindingExtensionTests { + static CompiledBindingExtensionTests() + { + RuntimeHelpers.RunClassConstructor(typeof(RelativeSource).TypeHandle); + } + [Fact] public void ResolvesClrPropertyBasedOnDataContextType() { diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index 49da2794c1..c421adaf21 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -23,7 +23,6 @@ namespace Avalonia.UnitTests platform: new AppBuilder().RuntimePlatform, renderInterface: new MockPlatformRenderInterface(), standardCursorFactory: Mock.Of(), - styler: new Styler(), theme: () => CreateSimpleTheme(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true), fontManagerImpl: new MockFontManagerImpl(), @@ -39,9 +38,6 @@ namespace Avalonia.UnitTests public static readonly TestServices MockPlatformWrapper = new TestServices( platform: Mock.Of()); - public static readonly TestServices MockStyler = new TestServices( - styler: Mock.Of()); - public static readonly TestServices MockThreadingInterface = new TestServices( threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)); @@ -58,9 +54,6 @@ namespace Avalonia.UnitTests fontManagerImpl: new MockFontManagerImpl(), textShaperImpl: new MockTextShaperImpl()); - public static readonly TestServices RealStyler = new TestServices( - styler: new Styler()); - public static readonly TestServices TextServices = new TestServices( assetLoader: new AssetLoader(), renderInterface: new MockPlatformRenderInterface(), @@ -80,7 +73,6 @@ namespace Avalonia.UnitTests IRenderTimer renderLoop = null, IScheduler scheduler = null, ICursorFactory standardCursorFactory = null, - IStyler styler = null, Func theme = null, IPlatformThreadingInterface threadingInterface = null, IFontManagerImpl fontManagerImpl = null, @@ -101,7 +93,6 @@ namespace Avalonia.UnitTests TextShaperImpl = textShaperImpl; Scheduler = scheduler; StandardCursorFactory = standardCursorFactory; - Styler = styler; Theme = theme; ThreadingInterface = threadingInterface; WindowImpl = windowImpl; @@ -121,7 +112,6 @@ namespace Avalonia.UnitTests public ITextShaperImpl TextShaperImpl { get; } public IScheduler Scheduler { get; } public ICursorFactory StandardCursorFactory { get; } - public IStyler Styler { get; } public Func Theme { get; } public IPlatformThreadingInterface ThreadingInterface { get; } public IWindowImpl WindowImpl { get; } @@ -140,8 +130,7 @@ namespace Avalonia.UnitTests IRenderTimer renderLoop = null, IScheduler scheduler = null, ICursorFactory standardCursorFactory = null, - IStyler styler = null, - Func theme = null, + Func theme = null, IPlatformThreadingInterface threadingInterface = null, IFontManagerImpl fontManagerImpl = null, ITextShaperImpl textShaperImpl = null, @@ -162,7 +151,6 @@ namespace Avalonia.UnitTests textShaperImpl: textShaperImpl ?? TextShaperImpl, scheduler: scheduler ?? Scheduler, standardCursorFactory: standardCursorFactory ?? StandardCursorFactory, - styler: styler ?? Styler, theme: theme ?? Theme, threadingInterface: threadingInterface ?? ThreadingInterface, windowingPlatform: windowingPlatform ?? WindowingPlatform, diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index 260771c9ab..03e19359c3 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -68,14 +68,13 @@ namespace Avalonia.UnitTests .Bind().ToConstant(Services.ThreadingInterface) .Bind().ToConstant(Services.Scheduler) .Bind().ToConstant(Services.StandardCursorFactory) - .Bind().ToConstant(Services.Styler) .Bind().ToConstant(Services.WindowingPlatform) .Bind().ToSingleton(); var theme = Services.Theme?.Invoke(); - if (theme is Styles styles) + if (theme is Style styles) { - Styles.AddRange(styles); + Styles.AddRange(styles.Children); } else if (theme is not null) { From 086c2c7e707df65c0ade4192b01b13a623bcee82 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 10 Nov 2022 11:25:44 +0100 Subject: [PATCH 120/136] Don't apply styling in TopLevel ctor. Applying styling in the constructor isn't a good idea as demonstrated by #8549.. Instead apply styling when showing a window, or if it's needed call `ApplyStyling` manually, e.g. in unit tests. Fixes #8549 --- src/Avalonia.Base/PropertyStore/ValueStore.cs | 3 +-- src/Avalonia.Controls/TopLevel.cs | 2 -- src/Avalonia.Controls/Window.cs | 2 ++ src/Avalonia.Controls/WindowBase.cs | 1 + .../AutoCompleteBoxTests.cs | 1 + tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs | 10 +++++++++- tests/Avalonia.Controls.UnitTests/MenuItemTests.cs | 3 +++ .../Avalonia.Controls.UnitTests/NumericUpDownTests.cs | 1 + .../Primitives/PopupRootTests.cs | 2 ++ .../Primitives/PopupTests.cs | 2 ++ tests/Avalonia.Controls.UnitTests/ToolTipTests.cs | 8 ++++++++ .../Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs | 7 ++++++- 12 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 8790991182..d858e30212 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -610,8 +610,7 @@ namespace Avalonia.PropertyStore private int InsertFrame(ValueFrame frame) { - // Uncomment this line when #8549 is fixed. - //Debug.Assert(!_frames.Contains(frame)); + Debug.Assert(!_frames.Contains(frame)); var index = BinarySearchFrame(frame.Priority); _frames.Insert(index, frame); diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 515535bf39..90af881adc 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -182,8 +182,6 @@ namespace Avalonia.Controls _globalStyles.GlobalStylesRemoved += ((IStyleHost)this).StylesRemoved; } - ApplyStyling(); - ClientSize = impl.ClientSize; FrameSize = impl.FrameSize; diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 1a7dca737e..559d674c02 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -647,6 +647,7 @@ namespace Avalonia.Controls RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); EnsureInitialized(); + ApplyStyling(); IsVisible = true; var initialSize = new Size( @@ -726,6 +727,7 @@ namespace Avalonia.Controls RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); EnsureInitialized(); + ApplyStyling(); IsVisible = true; var initialSize = new Size( diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 89483cd566..8f1b2198ad 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -149,6 +149,7 @@ namespace Avalonia.Controls try { EnsureInitialized(); + ApplyStyling(); IsVisible = true; if (!_hasExecutedInitialLayoutPass) diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index c8bd289e54..9d71e3bffc 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -1056,6 +1056,7 @@ namespace Avalonia.Controls.UnitTests control.Items = CreateSimpleStringArray(); TextBox textBox = GetTextBox(control); var window = new Window {Content = control}; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); Dispatcher.UIThread.RunJobs(); diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index b63cbd286e..a798801f20 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -29,6 +29,7 @@ namespace Avalonia.Controls.UnitTests }; var window = new Window { Content = target }; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -61,6 +62,7 @@ namespace Avalonia.Controls.UnitTests }; var window = new Window { Content = target }; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -130,6 +132,7 @@ namespace Avalonia.Controls.UnitTests }; var window = new Window { Content = target }; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -158,6 +161,7 @@ namespace Avalonia.Controls.UnitTests }; var window = new Window { Content = target }; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -186,6 +190,7 @@ namespace Avalonia.Controls.UnitTests }; var window = new Window { Content = target }; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -207,6 +212,7 @@ namespace Avalonia.Controls.UnitTests }; var window = new Window { Content = target }; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -390,7 +396,8 @@ namespace Avalonia.Controls.UnitTests var sp = new StackPanel { Children = { target1, target2 } }; var window = new Window { Content = sp }; - + + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -594,6 +601,7 @@ namespace Avalonia.Controls.UnitTests windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); var w = new Window(windowImpl.Object) { Content = content }; + w.ApplyStyling(); w.ApplyTemplate(); w.Presenter.ApplyTemplate(); return w; diff --git a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs index d25a790fde..c19a01facb 100644 --- a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs @@ -193,6 +193,7 @@ namespace Avalonia.Controls.UnitTests var target = new MenuItem(); var contextMenu = new ContextMenu { Items = new AvaloniaList { target } }; var window = new Window { Content = new Panel { ContextMenu = contextMenu } }; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -232,6 +233,7 @@ namespace Avalonia.Controls.UnitTests var flyout = new MenuFlyout { Items = new AvaloniaList { target } }; var button = new Button { Flyout = flyout }; var window = new Window { Content = button }; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -271,6 +273,7 @@ namespace Avalonia.Controls.UnitTests var parentMenuItem = new MenuItem { Items = new AvaloniaList { target } }; var contextMenu = new ContextMenu { Items = new AvaloniaList { parentMenuItem } }; var window = new Window { Content = new Panel { ContextMenu = contextMenu } }; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); contextMenu.Open(); diff --git a/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs b/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs index 4cef7e4d05..d50faf8be9 100644 --- a/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs +++ b/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs @@ -50,6 +50,7 @@ namespace Avalonia.Controls.UnitTests var control = CreateControl(); TextBox textBox = GetTextBox(control); var window = new Window { Content = control }; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); Dispatcher.UIThread.RunJobs(); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs index 6d3351d2b2..f283681088 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs @@ -51,6 +51,7 @@ namespace Avalonia.Controls.UnitTests.Primitives }; window.Content = target; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); target.ApplyTemplate(); @@ -177,6 +178,7 @@ namespace Avalonia.Controls.UnitTests.Primitives }; window.Content = target; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); target.ApplyTemplate(); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index 5f91f2e2a1..7e695612df 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -569,6 +569,7 @@ namespace Avalonia.Controls.UnitTests.Primitives windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); var window = new Window(windowImpl.Object); + window.ApplyStyling(); window.ApplyTemplate(); var target = new Popup() @@ -1090,6 +1091,7 @@ namespace Avalonia.Controls.UnitTests.Primitives private Window PreparedWindow(object content = null) { var w = new Window { Content = content }; + w.ApplyStyling(); w.ApplyTemplate(); return w; } diff --git a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs index 25969a58e3..25f38f8665 100644 --- a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs @@ -51,6 +51,7 @@ namespace Avalonia.Controls.UnitTests window.Content = panel; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -114,6 +115,7 @@ namespace Avalonia.Controls.UnitTests window.Content = target; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -140,6 +142,7 @@ namespace Avalonia.Controls.UnitTests window.Content = target; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -183,6 +186,7 @@ namespace Avalonia.Controls.UnitTests window.Content = target; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -215,6 +219,7 @@ namespace Avalonia.Controls.UnitTests window.Content = decorator; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -237,6 +242,7 @@ namespace Avalonia.Controls.UnitTests window.Content = decorator; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -261,6 +267,7 @@ namespace Avalonia.Controls.UnitTests window.Content = decorator; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); @@ -286,6 +293,7 @@ namespace Avalonia.Controls.UnitTests window.Content = target; + window.ApplyStyling(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 2a2e5f2478..af133cca73 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -720,7 +720,12 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml //ensure binding is set and operational first Assert.Equal(100.0, tracker.Tag); - Assert.Equal("EndInit 0", tracker.Order.Last()); + // EndInit should be second-to-last operation, as last operation will be + // caused by styling being applied on EndInit. + Assert.Equal("EndInit 0", tracker.Order[tracker.Order.Count - 2]); + + // Caused by styling. + Assert.Equal("Property Foreground Changed", tracker.Order[tracker.Order.Count - 1]); } } From 05d3786116b27049a39098bd1338b48f36ef6548 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 21 Nov 2022 13:05:57 +0100 Subject: [PATCH 121/136] *Web* projects were renamed to *Browser*. --- ...3.ncrunchproject => Avalonia.Browser.Blazor.v3.ncrunchproject} | 0 ...a.Web.v3.ncrunchproject => Avalonia.Browser.v3.ncrunchproject} | 0 ...nchproject => ControlCatalog.Browser.Blazor.v3.ncrunchproject} | 0 ...v3.ncrunchproject => ControlCatalog.Browser.v3.ncrunchproject} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename .ncrunch/{Avalonia.Web.Blazor.v3.ncrunchproject => Avalonia.Browser.Blazor.v3.ncrunchproject} (100%) rename .ncrunch/{Avalonia.Web.v3.ncrunchproject => Avalonia.Browser.v3.ncrunchproject} (100%) rename .ncrunch/{ControlCatalog.Blazor.Web.v3.ncrunchproject => ControlCatalog.Browser.Blazor.v3.ncrunchproject} (100%) rename .ncrunch/{ControlCatalog.Web.v3.ncrunchproject => ControlCatalog.Browser.v3.ncrunchproject} (100%) diff --git a/.ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject b/.ncrunch/Avalonia.Browser.Blazor.v3.ncrunchproject similarity index 100% rename from .ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject rename to .ncrunch/Avalonia.Browser.Blazor.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.Web.v3.ncrunchproject b/.ncrunch/Avalonia.Browser.v3.ncrunchproject similarity index 100% rename from .ncrunch/Avalonia.Web.v3.ncrunchproject rename to .ncrunch/Avalonia.Browser.v3.ncrunchproject diff --git a/.ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject b/.ncrunch/ControlCatalog.Browser.Blazor.v3.ncrunchproject similarity index 100% rename from .ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject rename to .ncrunch/ControlCatalog.Browser.Blazor.v3.ncrunchproject diff --git a/.ncrunch/ControlCatalog.Web.v3.ncrunchproject b/.ncrunch/ControlCatalog.Browser.v3.ncrunchproject similarity index 100% rename from .ncrunch/ControlCatalog.Web.v3.ncrunchproject rename to .ncrunch/ControlCatalog.Browser.v3.ncrunchproject From 2c085b6e12a70a1dc9ee35e7e3dd3cf5289ed792 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 21 Nov 2022 13:08:33 +0100 Subject: [PATCH 122/136] Don't instrument theme projects. Was failing with an `IndexOutOfRangeException` inside ncrunch. It's not important to have code coverage for these projects anyway. --- .ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject | 5 +++++ .../Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject | 5 +++++ .ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject | 5 +++++ .../Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject | 5 +++++ 4 files changed, 20 insertions(+) create mode 100644 .ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject create mode 100644 .ncrunch/Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject create mode 100644 .ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject create mode 100644 .ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject new file mode 100644 index 0000000000..02eb0d211e --- /dev/null +++ b/.ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + False + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject new file mode 100644 index 0000000000..02eb0d211e --- /dev/null +++ b/.ncrunch/Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + False + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject new file mode 100644 index 0000000000..02eb0d211e --- /dev/null +++ b/.ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + False + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject new file mode 100644 index 0000000000..02eb0d211e --- /dev/null +++ b/.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + False + + \ No newline at end of file From ca7b99ef48be17bd88266eaffd7de93191d52282 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 21 Nov 2022 13:41:01 +0100 Subject: [PATCH 123/136] Ignore a few more projects. That aren't needed for tests to run. --- .ncrunch/Avalonia.Benchmarks.v3.ncrunchproject | 5 +++++ .ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject | 5 +++++ .ncrunch/MobileSandbox.v3.ncrunchproject | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 .ncrunch/Avalonia.Benchmarks.v3.ncrunchproject create mode 100644 .ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject create mode 100644 .ncrunch/MobileSandbox.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.Benchmarks.v3.ncrunchproject b/.ncrunch/Avalonia.Benchmarks.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.Benchmarks.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject b/.ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/MobileSandbox.v3.ncrunchproject b/.ncrunch/MobileSandbox.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/MobileSandbox.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file From 273124603f184b8de42a22067b7dfc560f7d9792 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 21 Nov 2022 15:03:33 +0100 Subject: [PATCH 124/136] Added benchmark for changing control theme. --- .../Styling/ControlTheme_Change.cs | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 tests/Avalonia.Benchmarks/Styling/ControlTheme_Change.cs diff --git a/tests/Avalonia.Benchmarks/Styling/ControlTheme_Change.cs b/tests/Avalonia.Benchmarks/Styling/ControlTheme_Change.cs new file mode 100644 index 0000000000..627edfdeb6 --- /dev/null +++ b/tests/Avalonia.Benchmarks/Styling/ControlTheme_Change.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Styling; +using Avalonia.UnitTests; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks.Styling +{ + [MemoryDiagnoser] + public class ControlTheme_Change : IDisposable + { + private readonly IDisposable _app; + private readonly TestRoot _root; + private readonly TextBox _control; + private readonly ControlTheme _theme1; + private readonly ControlTheme _theme2; + + public ControlTheme_Change() + { + _app = UnitTestApplication.Start( + TestServices.StyledWindow.With( + renderInterface: new NullRenderingPlatform(), + threadingInterface: new NullThreadingPlatform())); + + // Simulate an application with a lot of styles by creating a tree of nested panels, + // each with a bunch of styles applied. + var (rootPanel, leafPanel) = CreateNestedPanels(10); + + // We're benchmarking how long it takes to switch control theme on a TextBox in this + // situation. + var baseTheme = (ControlTheme)Application.Current.FindResource(typeof(TextBox)) ?? + throw new Exception("Base TextBox theme not found."); + + _theme1 = new ControlTheme(typeof(TextBox)) + { + BasedOn = baseTheme, + Setters = { new Setter(TextBox.BackgroundProperty, Brushes.Red) }, + }; + + _theme2 = new ControlTheme(typeof(TextBox)) + { + BasedOn = baseTheme, + Setters = { new Setter(TextBox.BackgroundProperty, Brushes.Green) }, + }; + + _control = new TextBox { Theme = _theme1 }; + leafPanel.Children.Add(_control); + + _root = new TestRoot(true, rootPanel) + { + Renderer = new NullRenderer(), + }; + + _root.LayoutManager.ExecuteInitialLayoutPass(); + } + + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public void Change_ControlTheme() + { + if (_control.Background != Brushes.Red) + throw new Exception("Invalid benchmark state"); + + _control.Theme = _theme2; + _root.LayoutManager.ExecuteLayoutPass(); + + if (_control.Background != Brushes.Green) + throw new Exception("Invalid benchmark state"); + + _control.Theme = _theme1; + _root.LayoutManager.ExecuteLayoutPass(); + + if (_control.Background != Brushes.Red) + throw new Exception("Invalid benchmark state"); + } + + public void Dispose() + { + _app.Dispose(); + } + + private static (Panel, Panel) CreateNestedPanels(int count) + { + var root = new Panel(); + var last = root; + + for (var i = 0; i < count; ++i) + { + var panel = new Panel(); + panel.Styles.AddRange(CreateStyles()); + last.Children.Add(panel); + last = panel; + } + + return (root, last); + } + + private static IEnumerable CreateStyles() + { + var types = new[] + { + typeof(Border), + typeof(Button), + typeof(ButtonSpinner), + typeof(Carousel), + typeof(CheckBox), + typeof(ComboBox), + typeof(ContentControl), + typeof(Expander), + typeof(ItemsControl), + typeof(Label), + typeof(ListBox), + typeof(ProgressBar), + typeof(RadioButton), + typeof(RepeatButton), + typeof(ScrollViewer), + typeof(Slider), + typeof(Spinner), + typeof(SplitView), + typeof(TextBox), + typeof(ToggleSwitch), + typeof(TreeView), + typeof(Viewbox), + typeof(Window), + }; + + foreach (var type in types) + { + yield return new Style(x => x.OfType(type)) + { + Setters = { new Setter(Control.TagProperty, type.Name) } + }; + + yield return new Style(x => x.OfType(type).Class("foo")) + { + Setters = { new Setter(Control.TagProperty, type.Name + " foo") } + }; + + yield return new Style(x => x.OfType(type).Class("bar")) + { + Setters = { new Setter(Control.TagProperty, type.Name + " bar") } + }; + } + } + } +} From 326dac232899a17d7f6ae8852c81357282af0cfa Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 18 Nov 2022 12:02:32 +0100 Subject: [PATCH 125/136] Refactored how we switch control themes. Instead of simply wiping all control themes and styles that are applied to a control, we can now just remove the `ValueFrame`s which relate to the control theme that was changed. To do this, added `ValueFrame.FramePriority` which encodes both the `BindingPriority` and source of the frame (style, control theme, templated parent control theme). --- src/Avalonia.Base/Layout/Layoutable.cs | 7 + .../PropertyStore/FramePriority.cs | 36 +++ .../PropertyStore/ImmediateValueFrame.cs | 2 +- src/Avalonia.Base/PropertyStore/ValueFrame.cs | 20 +- src/Avalonia.Base/PropertyStore/ValueStore.cs | 33 ++- src/Avalonia.Base/StyledElement.cs | 95 ++++---- src/Avalonia.Base/Styling/ControlTheme.cs | 7 +- src/Avalonia.Base/Styling/Style.cs | 5 +- src/Avalonia.Base/Styling/StyleBase.cs | 6 +- src/Avalonia.Base/Styling/StyleInstance.cs | 13 +- src/Avalonia.Base/Styling/Styles.cs | 3 +- .../Primitives/TemplatedControl.cs | 59 ++--- .../Animation/AnimatableTests.cs | 3 +- .../PropertyStore/ValueStoreTests_Frames.cs | 2 +- .../Styling/SetterTests.cs | 3 +- .../Styling/StyleTests.cs | 17 +- .../Styling/StyledElementTests_Theming.cs | 218 +++++++++++++++++- .../Styling/Style_Activation.cs | 3 +- .../Styling/Style_Apply.cs | 3 +- .../Styling/Style_ClassSelector.cs | 7 +- .../Styling/Style_NonActive.cs | 3 +- .../StyleTests.cs | 3 +- 22 files changed, 410 insertions(+), 138 deletions(-) create mode 100644 src/Avalonia.Base/PropertyStore/FramePriority.cs diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs index 527b63292d..d09d1dc8d2 100644 --- a/src/Avalonia.Base/Layout/Layoutable.cs +++ b/src/Avalonia.Base/Layout/Layoutable.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Logging; +using Avalonia.Styling; using Avalonia.VisualTree; #nullable enable @@ -795,6 +796,12 @@ namespace Avalonia.Layout base.OnVisualParentChanged(oldParent, newParent); } + private protected override void OnControlThemeChanged() + { + base.OnControlThemeChanged(); + InvalidateMeasure(); + } + /// /// Called when the layout manager raises a LayoutUpdated event. /// diff --git a/src/Avalonia.Base/PropertyStore/FramePriority.cs b/src/Avalonia.Base/PropertyStore/FramePriority.cs new file mode 100644 index 0000000000..950a8375f2 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/FramePriority.cs @@ -0,0 +1,36 @@ +using System.Diagnostics; +using Avalonia.Data; + +namespace Avalonia.PropertyStore +{ + internal enum FramePriority : sbyte + { + Animation, + AnimationTemplatedParentTheme, + AnimationTheme, + StyleTrigger, + StyleTriggerTemplatedParentTheme, + StyleTriggerTheme, + Template, + TemplateTemplatedParentTheme, + TemplateTheme, + Style, + StyleTemplatedParentTheme, + StyleTheme, + } + + internal static class FramePriorityExtensions + { + public static FramePriority ToFramePriority(this BindingPriority priority, FrameType type = FrameType.Style) + { + Debug.Assert(priority != BindingPriority.LocalValue); + var p = (int)(priority > 0 ? priority : priority + 1); + return (FramePriority)(p * 3 + (int)type); + } + + public static bool IsType(this FramePriority priority, FrameType type) + { + return (FrameType)((int)priority % 3) == type; + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs index 1d886e7501..756ab7aadf 100644 --- a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs @@ -10,8 +10,8 @@ namespace Avalonia.PropertyStore internal class ImmediateValueFrame : ValueFrame { public ImmediateValueFrame(BindingPriority priority) + : base(priority, FrameType.Style) { - Priority = priority; } public TypedBindingEntry AddBinding( diff --git a/src/Avalonia.Base/PropertyStore/ValueFrame.cs b/src/Avalonia.Base/PropertyStore/ValueFrame.cs index 5ada4b3c84..7a9d1bb13a 100644 --- a/src/Avalonia.Base/PropertyStore/ValueFrame.cs +++ b/src/Avalonia.Base/PropertyStore/ValueFrame.cs @@ -1,13 +1,18 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Avalonia.Data; using Avalonia.Utilities; -using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot; namespace Avalonia.PropertyStore { + internal enum FrameType + { + Style, + TemplatedParentTheme, + Theme, + } + internal abstract class ValueFrame { private List? _entries; @@ -15,11 +20,18 @@ namespace Avalonia.PropertyStore private ValueStore? _owner; private bool _isShared; + protected ValueFrame(BindingPriority priority, FrameType type) + { + Priority = priority; + FramePriority = priority.ToFramePriority(type); + } + public int EntryCount => _index.Count; public bool IsActive => GetIsActive(out _); public ValueStore? Owner => !_isShared ? _owner : throw new AvaloniaInternalException("Cannot get owner for shared ValueFrame"); - public BindingPriority Priority { get; protected set; } + public BindingPriority Priority { get; } + public FramePriority FramePriority { get; } public bool Contains(AvaloniaProperty property) => _index.ContainsKey(property); diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index d858e30212..e14d018564 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -4,8 +4,8 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Avalonia.Data; using Avalonia.Diagnostics; -using Avalonia.Logging; using Avalonia.Utilities; +using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot; namespace Avalonia.PropertyStore { @@ -580,6 +580,29 @@ namespace Avalonia.PropertyStore return false; } + public void RemoveFrames(FrameType type) + { + var removed = false; + + for (var i = _frames.Count - 1; i >= 0; --i) + { + var frame = _frames[i]; + + if (frame.FramePriority.IsType(type)) + { + _frames.RemoveAt(i); + frame.Dispose(); + removed = true; + } + } + + if (removed) + { + ++_frameGeneration; + ReevaluateEffectiveValues(); + } + } + public AvaloniaPropertyValue GetDiagnostic(AvaloniaProperty property) { object? value; @@ -612,7 +635,7 @@ namespace Avalonia.PropertyStore { Debug.Assert(!_frames.Contains(frame)); - var index = BinarySearchFrame(frame.Priority); + var index = BinarySearchFrame(frame.FramePriority); _frames.Insert(index, frame); ++_frameGeneration; frame.SetOwner(this); @@ -626,7 +649,7 @@ namespace Avalonia.PropertyStore { Debug.Assert(priority != BindingPriority.LocalValue); - var index = BinarySearchFrame(priority); + var index = BinarySearchFrame(priority.ToFramePriority()); if (index > 0 && _frames[index - 1] is ImmediateValueFrame f && f.Priority == priority && @@ -914,7 +937,7 @@ namespace Avalonia.PropertyStore } } - private int BinarySearchFrame(BindingPriority priority) + private int BinarySearchFrame(FramePriority priority) { var lo = 0; var hi = _frames.Count - 1; @@ -923,7 +946,7 @@ namespace Avalonia.PropertyStore while (lo <= hi) { var i = lo + ((hi - lo) >> 1); - var order = priority - _frames[i].Priority; + var order = priority - _frames[i].FramePriority; if (order <= 0) { diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index c72f398fd9..33bca9b0ab 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using Avalonia.Animation; using Avalonia.Collections; @@ -11,6 +12,7 @@ using Avalonia.Data; using Avalonia.Diagnostics; using Avalonia.Logging; using Avalonia.LogicalTree; +using Avalonia.PropertyStore; using Avalonia.Styling; namespace Avalonia @@ -69,10 +71,10 @@ namespace Avalonia private IAvaloniaList? _logicalChildren; private IResourceDictionary? _resources; private Styles? _styles; - private bool _styled; + private bool _stylesApplied; + private bool _themeApplied; private ITemplatedControl? _templatedParent; private bool _dataContextUpdating; - private bool _hasPromotedTheme; private ControlTheme? _implicitTheme; /// @@ -141,7 +143,7 @@ namespace Avalonia set { - if (_styled) + if (_stylesApplied) { throw new InvalidOperationException("Cannot set Name : styled element already styled."); } @@ -353,31 +355,31 @@ namespace Avalonia /// public bool ApplyStyling() { - if (_initCount == 0 && !_styled) + if (_initCount == 0 && (!_stylesApplied || !_themeApplied)) { - var hasPromotedTheme = _hasPromotedTheme; - GetValueStore().BeginStyling(); try { - ApplyControlTheme(); - ApplyStyles(this); + if (!_themeApplied) + { + ApplyControlTheme(); + _themeApplied = true; + } + + if (!_stylesApplied) + { + ApplyStyles(this); + _stylesApplied = true; + } } finally { - _styled = true; GetValueStore().EndStyling(); } - - if (hasPromotedTheme) - { - _hasPromotedTheme = false; - ClearValue(ThemeProperty); - } } - return _styled; + return _stylesApplied; } /// @@ -615,31 +617,25 @@ namespace Avalonia if (change.Property == ThemeProperty) { - var (oldValue, newValue) = change.GetOldAndNewValue(); - - // Changing the theme detaches all styles, meaning that if the theme property was - // set via a style, it will get cleared! To work around this, if the value was - // applied at less than local value priority then promote the value to local value - // priority until styling is re-applied. - if (change.Priority > BindingPriority.LocalValue) - { - Theme = newValue; - _hasPromotedTheme = true; - } - else if (_hasPromotedTheme && change.Priority == BindingPriority.LocalValue) - { - _hasPromotedTheme = false; - } - - InvalidateStyles(); - - if (oldValue is not null) - DetachControlThemeFromTemplateChildren(oldValue); + OnControlThemeChanged(); + _themeApplied = false; } } - internal virtual void DetachControlThemeFromTemplateChildren(ControlTheme theme) + private protected virtual void OnControlThemeChanged() { + var values = GetValueStore(); + values.BeginStyling(); + try { values.RemoveFrames(FrameType.Theme); } + finally { values.EndStyling(); } + } + + internal virtual void OnTemplatedParentControlThemeChanged() + { + var values = GetValueStore(); + values.BeginStyling(); + try { values.RemoveFrames(FrameType.TemplatedParentTheme); } + finally { values.EndStyling(); } } internal ControlTheme? GetEffectiveTheme() @@ -736,26 +732,28 @@ namespace Avalonia var theme = GetEffectiveTheme(); if (theme is not null) - ApplyControlTheme(theme); + ApplyControlTheme(theme, FrameType.Theme); if (TemplatedParent is StyledElement styleableParent && styleableParent.GetEffectiveTheme() is { } parentTheme) { - ApplyControlTheme(parentTheme); + ApplyControlTheme(parentTheme, FrameType.TemplatedParentTheme); } } - private void ApplyControlTheme(ControlTheme theme) + private void ApplyControlTheme(ControlTheme theme, FrameType type) { + Debug.Assert(type is FrameType.Theme or FrameType.TemplatedParentTheme); + if (theme.BasedOn is ControlTheme basedOn) - ApplyControlTheme(basedOn); + ApplyControlTheme(basedOn, type); - theme.TryAttach(this, null); + theme.TryAttach(this, type); if (theme.HasChildren) { foreach (var child in theme.Children) - ApplyStyle(child, null); + ApplyStyle(child, null, type); } } @@ -768,17 +766,17 @@ namespace Avalonia if (host.IsStylesInitialized) { foreach (var style in host.Styles) - ApplyStyle(style, host); + ApplyStyle(style, host, FrameType.Style); } } - private void ApplyStyle(IStyle style, IStyleHost? host) + private void ApplyStyle(IStyle style, IStyleHost? host, FrameType type) { if (style is Style s) - s.TryAttach(this, host); + s.TryAttach(this, host, type); foreach (var child in style.Children) - ApplyStyle(child, host); + ApplyStyle(child, host, type); } private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e) @@ -895,6 +893,7 @@ namespace Avalonia for (var i = valueStore.Frames.Count - 1; i >= 0; --i) { if (valueStore.Frames[i] is StyleInstance si && + si.Source is not ControlTheme && (styles is null || styles.Contains(si.Source))) { valueStore.RemoveFrame(si); @@ -902,7 +901,7 @@ namespace Avalonia } valueStore.EndStyling(); - _styled = false; + _stylesApplied = false; } private void InvalidateStylesOnThisAndDescendents() diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs index 2971703c95..5fc900d2cb 100644 --- a/src/Avalonia.Base/Styling/ControlTheme.cs +++ b/src/Avalonia.Base/Styling/ControlTheme.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using Avalonia.PropertyStore; namespace Avalonia.Styling @@ -36,8 +37,10 @@ namespace Avalonia.Styling throw new InvalidOperationException("ControlThemes cannot be added as a nested style."); } - internal override SelectorMatchResult TryAttach(IStyleable target, object? host) + internal SelectorMatchResult TryAttach(IStyleable target, FrameType type) { + Debug.Assert(type is FrameType.Theme or FrameType.TemplatedParentTheme); + _ = target ?? throw new ArgumentNullException(nameof(target)); if (TargetType is null) @@ -45,7 +48,7 @@ namespace Avalonia.Styling if (HasSettersOrAnimations && TargetType.IsAssignableFrom(target.StyleKey)) { - Attach(target, null); + Attach(target, null, type); return SelectorMatchResult.AlwaysThisType; } diff --git a/src/Avalonia.Base/Styling/Style.cs b/src/Avalonia.Base/Styling/Style.cs index aad91824d3..15d8a9fe2e 100644 --- a/src/Avalonia.Base/Styling/Style.cs +++ b/src/Avalonia.Base/Styling/Style.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.PropertyStore; namespace Avalonia.Styling { @@ -58,7 +59,7 @@ namespace Avalonia.Styling base.SetParent(parent); } - internal override SelectorMatchResult TryAttach(IStyleable target, object? host) + internal SelectorMatchResult TryAttach(IStyleable target, object? host, FrameType type) { _ = target ?? throw new ArgumentNullException(nameof(target)); @@ -73,7 +74,7 @@ namespace Avalonia.Styling if (match.IsMatch) { - Attach(target, match.Activator); + Attach(target, match.Activator, type); } result = match.Result; diff --git a/src/Avalonia.Base/Styling/StyleBase.cs b/src/Avalonia.Base/Styling/StyleBase.cs index dba80df2e5..83fcf04d2f 100644 --- a/src/Avalonia.Base/Styling/StyleBase.cs +++ b/src/Avalonia.Base/Styling/StyleBase.cs @@ -92,9 +92,7 @@ namespace Avalonia.Styling return false; } - internal abstract SelectorMatchResult TryAttach(IStyleable target, object? host); - - internal ValueFrame Attach(IStyleable target, IStyleActivator? activator) + internal ValueFrame Attach(IStyleable target, IStyleActivator? activator, FrameType type) { if (target is not AvaloniaObject ao) throw new InvalidOperationException("Styles can only be applied to AvaloniaObjects."); @@ -109,7 +107,7 @@ namespace Avalonia.Styling { var canShareInstance = activator is null; - instance = new StyleInstance(this, activator); + instance = new StyleInstance(this, activator, type); if (_setters is not null) { diff --git a/src/Avalonia.Base/Styling/StyleInstance.cs b/src/Avalonia.Base/Styling/StyleInstance.cs index 2d7c695b32..4985aa16c7 100644 --- a/src/Avalonia.Base/Styling/StyleInstance.cs +++ b/src/Avalonia.Base/Styling/StyleInstance.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Reactive.Subjects; using Avalonia.Animation; using Avalonia.Data; @@ -27,10 +26,13 @@ namespace Avalonia.Styling private List? _animations; private Subject? _animationTrigger; - public StyleInstance(IStyle style, IStyleActivator? activator) + public StyleInstance( + IStyle style, + IStyleActivator? activator, + FrameType type) + : base(GetPriority(activator), type) { _activator = activator; - Priority = activator is object ? BindingPriority.StyleTrigger : BindingPriority.Style; Source = style; } @@ -99,5 +101,10 @@ namespace Avalonia.Styling hasChanged = _isActive != previous; return _isActive; } + + private static BindingPriority GetPriority(IStyleActivator? activator) + { + return activator is not null ? BindingPriority.StyleTrigger : BindingPriority.Style; + } } } diff --git a/src/Avalonia.Base/Styling/Styles.cs b/src/Avalonia.Base/Styling/Styles.cs index 76271b9748..f22bbc0eae 100644 --- a/src/Avalonia.Base/Styling/Styles.cs +++ b/src/Avalonia.Base/Styling/Styles.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using Avalonia.Collections; using Avalonia.Controls; +using Avalonia.PropertyStore; namespace Avalonia.Styling { @@ -233,7 +234,7 @@ namespace Avalonia.Styling { if (s is not Style style) continue; - var r = style.TryAttach(target, host); + var r = style.TryAttach(target, host, FrameType.Style); if (r > result) result = r; } diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 80151fbfb3..17d90e6dd0 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -5,6 +5,7 @@ using Avalonia.Interactivity; using Avalonia.Logging; using Avalonia.LogicalTree; using Avalonia.Media; +using Avalonia.PropertyStore; using Avalonia.Styling; using Avalonia.VisualTree; @@ -395,56 +396,36 @@ namespace Avalonia.Controls.Primitives } } - internal override void DetachControlThemeFromTemplateChildren(ControlTheme theme) + private protected override void OnControlThemeChanged() { - static ControlTheme? GetControlTheme(StyleBase style) - { - var s = style; + base.OnControlThemeChanged(); - while (s is not null) + var count = VisualChildren.Count; + for (var i = 0; i < count; ++i) + { + if (VisualChildren[i] is StyledElement child && + child.TemplatedParent == this) { - if (s is ControlTheme c) - return c; - s = s.Parent as StyleBase; + child.OnTemplatedParentControlThemeChanged(); } - - return null; } + } - static void Detach(Visual control, ITemplatedControl templatedParent, ControlTheme theme) - { - var valueStore = control.GetValueStore(); - var count = valueStore.Frames.Count; - - if (control != templatedParent) - { - valueStore.BeginStyling(); - - for (var i = count - 1; i >= 0; --i) - { - if (valueStore.Frames[i] is StyleInstance si && - si.Source is StyleBase style && - GetControlTheme(style) == theme) - { - valueStore.RemoveFrame(si); - } - } - - valueStore.EndStyling(); - } + internal override void OnTemplatedParentControlThemeChanged() + { + base.OnTemplatedParentControlThemeChanged(); - var children = ((IVisual)control).VisualChildren; - count = children.Count; + var count = VisualChildren.Count; + var templatedParent = TemplatedParent; - for (var i = 0; i < count; i++) + for (var i = 0; i < count; ++i) + { + if (VisualChildren[i] is TemplatedControl child && + child.TemplatedParent == templatedParent) { - if (children[i] is Visual v && - v.TemplatedParent == templatedParent) - Detach(v, templatedParent, theme); + child.OnTemplatedParentControlThemeChanged(); } } - - Detach(this, this, theme); } } } diff --git a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs index 668b8a875c..dec813b3b0 100644 --- a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs +++ b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs @@ -5,6 +5,7 @@ using Avalonia.Controls.Shapes; using Avalonia.Data; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.PropertyStore; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -435,7 +436,7 @@ namespace Avalonia.Base.UnitTests.Animation } }; - style.TryAttach(control, control); + style.TryAttach(control, control, FrameType.Style); // Which means that the transition state hasn't been initialized with the new // Transitions when the Opacity change notification gets raised here. diff --git a/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs b/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs index bb726a1d63..3a307447ac 100644 --- a/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs +++ b/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs @@ -117,7 +117,7 @@ namespace Avalonia.Base.UnitTests.PropertyStore private static StyleInstance InstanceStyle(Style style, StyledElement target) { - var result = new StyleInstance(style, null); + var result = new StyleInstance(style, null, FrameType.Style); foreach (var setter in style.Setters) result.Add(setter.Instance(result, target)); diff --git a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs index dc31d3d3ec..18f572dedc 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Data.Converters; using Avalonia.Media; +using Avalonia.PropertyStore; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -503,7 +504,7 @@ namespace Avalonia.Base.UnitTests.Styling private void Apply(Style style, Control control) { - style.TryAttach(control, null); + style.TryAttach(control, null, FrameType.Style); } private void Apply(Setter setter, Control control) diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs b/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs index e7ecba61a7..a318a8d76a 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs @@ -5,6 +5,7 @@ using Avalonia.Base.UnitTests.Animation; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.PropertyStore; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -27,7 +28,7 @@ namespace Avalonia.Base.UnitTests.Styling var target = new Class1(); - style.TryAttach(target, null); + style.TryAttach(target, null, FrameType.Style); Assert.Equal("Foo", target.Foo); } @@ -45,7 +46,7 @@ namespace Avalonia.Base.UnitTests.Styling var target = new Class1(); - style.TryAttach(target, null); + style.TryAttach(target, null, FrameType.Style); Assert.Equal("foodefault", target.Foo); target.Classes.Add("foo"); Assert.Equal("Foo", target.Foo); @@ -66,7 +67,7 @@ namespace Avalonia.Base.UnitTests.Styling var target = new Class1(); - style.TryAttach(target, target); + style.TryAttach(target, target, FrameType.Style); Assert.Equal("Foo", target.Foo); } @@ -92,7 +93,7 @@ namespace Avalonia.Base.UnitTests.Styling var target = new Class1(); var other = new Class1(); - style.TryAttach(target, other); + style.TryAttach(target, other, FrameType.Style); Assert.Equal("foodefault", target.Foo); } @@ -113,7 +114,7 @@ namespace Avalonia.Base.UnitTests.Styling Foo = "Original", }; - style.TryAttach(target, null); + style.TryAttach(target, null, FrameType.Style); Assert.Equal("Original", target.Foo); } @@ -577,7 +578,7 @@ namespace Avalonia.Base.UnitTests.Styling Child = border = new Border(), }; - style.TryAttach(border, null); + style.TryAttach(border, null, FrameType.Style); Assert.Equal(new Thickness(4), border.BorderThickness); root.Child = null; @@ -761,7 +762,7 @@ namespace Avalonia.Base.UnitTests.Styling var target = new Class1(); - style.TryAttach(target, null); + style.TryAttach(target, null, FrameType.Style); Assert.Equal(1, target.Classes.ListenerCount); @@ -874,7 +875,7 @@ namespace Avalonia.Base.UnitTests.Styling var clock = new TestClock(); var target = new Class1 { Clock = clock }; - style.TryAttach(target, null); + style.TryAttach(target, null, FrameType.Style); Assert.Equal(0.0, target.Double); diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs index a1dac931ce..b5524affb7 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs @@ -23,7 +23,7 @@ public class StyledElementTests_Theming Assert.Null(target.Template); - var root = CreateRoot(target); + CreateRoot(target); Assert.NotNull(target.Template); var border = Assert.IsType(target.VisualChild); @@ -43,7 +43,7 @@ public class StyledElementTests_Theming Assert.Null(target.Template); - var root = CreateRoot(target); + CreateRoot(target); Assert.NotNull(target.Template); var border = Assert.IsType(target.VisualChild); @@ -57,7 +57,7 @@ public class StyledElementTests_Theming public void Theme_Is_Detached_When_Theme_Property_Cleared() { var target = CreateTarget(); - var root = CreateRoot(target); + CreateRoot(target); Assert.NotNull(target.Template); @@ -66,7 +66,47 @@ public class StyledElementTests_Theming } [Fact] - public void Theme_Is_Detached_From_Template_Controls_When_Theme_Property_Cleared() + public void Setting_Explicit_Theme_Detaches_Default_Theme() + { + var target = new ThemedControl(); + var root = new TestRoot + { + Resources = { { typeof(ThemedControl), CreateTheme() } }, + Child = target, + }; + + root.LayoutManager.ExecuteInitialLayoutPass(); + + Assert.Equal("theme", target.Tag); + + target.Theme = new ControlTheme(typeof(ThemedControl)) + { + Setters = + { + new Setter(ThemedControl.BackgroundProperty, Brushes.Yellow), + } + }; + + root.LayoutManager.ExecuteLayoutPass(); + + Assert.Null(target.Tag); + Assert.Equal(Brushes.Yellow, target.Background); + } + + [Fact] + public void Unrelated_Styles_Are_Not_Detached_When_Theme_Property_Cleared() + { + var target = CreateTarget(); + CreateRoot(target, createAdditionalStyles: true); + + Assert.Equal("style", target.Tag); + + target.Theme = null; + Assert.Equal("style", target.Tag); + } + + [Fact] + public void TemplatedParent_Theme_Is_Detached_From_Template_Controls_When_Theme_Property_Cleared() { var theme = new ControlTheme { @@ -93,10 +133,115 @@ public class StyledElementTests_Theming target.Theme = null; - Assert.IsType(target.VisualChild); + Assert.Same(canvas, target.VisualChild); Assert.Null(canvas.Background); } + [Fact] + public void Primary_Theme_Is_Not_Detached_From_Template_Controls_When_Theme_Property_Cleared() + { + var templatedParentTheme = new ControlTheme + { + TargetType = typeof(ThemedControl), + Children = + { + new Style(x => x.Nesting().Template().OfType - internal class ImmediateValueFrame : ValueFrame + internal sealed class ImmediateValueFrame : ValueFrame { public ImmediateValueFrame(BindingPriority priority) : base(priority, FrameType.Style) diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index e14d018564..92e5288255 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using Avalonia.Data; using Avalonia.Diagnostics; +using Avalonia.Styling; using Avalonia.Utilities; using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot; @@ -588,7 +590,31 @@ namespace Avalonia.PropertyStore { var frame = _frames[i]; - if (frame.FramePriority.IsType(type)) + if (frame is not ImmediateValueFrame && frame.FramePriority.IsType(type)) + { + _frames.RemoveAt(i); + frame.Dispose(); + removed = true; + } + } + + if (removed) + { + ++_frameGeneration; + ReevaluateEffectiveValues(); + } + } + + + public void RemoveFrames(IReadOnlyList styles) + { + var removed = false; + + for (var i = _frames.Count - 1; i >= 0; --i) + { + var frame = _frames[i]; + + if (frame is StyleInstance style && styles.Contains(style.Source)) { _frames.RemoveAt(i); frame.Dispose(); diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 33bca9b0ab..187edd8335 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -382,11 +382,6 @@ namespace Avalonia return _stylesApplied; } - /// - /// Detaches all styles from the element and queues a restyle. - /// - protected virtual void InvalidateStyles() => DetachStyles(); - protected void InitializeIfNeeded() { if (_initCount == 0 && !IsInitialized) @@ -508,17 +503,16 @@ namespace Avalonia }; } - void IStyleable.DetachStyles() => DetachStyles(); - void IStyleHost.StylesAdded(IReadOnlyList styles) { - InvalidateStylesOnThisAndDescendents(); + if (HasSettersOrAnimations(styles)) + InvalidateStyles(recurse: true); } void IStyleHost.StylesRemoved(IReadOnlyList styles) { - var allStyles = RecurseStyles(styles); - DetachStylesFromThisAndDescendents(allStyles); + if (FlattenStyles(styles) is { } allStyles) + DetachStyles(allStyles); } protected virtual void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) @@ -663,6 +657,23 @@ namespace Avalonia return null; } + internal virtual void InvalidateStyles(bool recurse) + { + var values = GetValueStore(); + values.BeginStyling(); + try { values.RemoveFrames(FrameType.Style); } + finally { values.EndStyling(); } + + _stylesApplied = false; + + if (recurse && GetInheritanceChildren() is { } children) + { + var childCount = children.Count; + for (var i = 0; i < childCount; ++i) + (children[i] as StyledElement)?.InvalidateStyles(recurse); + } + } + private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted) { if (o is StyledElement element) @@ -822,7 +833,7 @@ namespace Avalonia { _logicalRoot = null; _implicitTheme = null; - DetachStyles(); + InvalidateStyles(recurse: false); OnDetachedFromLogicalTree(e); DetachedFromLogicalTree?.Invoke(this, e); @@ -884,71 +895,81 @@ namespace Avalonia } } - private void DetachStyles(IReadOnlyList? styles = null) + private void DetachStyles(IReadOnlyList