diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 4be1419f78..7df9b76425 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -29,10 +29,12 @@ public: IAvnMenu* _mainMenu; bool _shown; + bool _inResize; WindowBaseImpl(IAvnWindowBaseEvents* events, IAvnGlContext* gl) { _shown = false; + _inResize = false; _mainMenu = nullptr; BaseEvents = events; _glContext = gl; @@ -277,6 +279,13 @@ public: virtual HRESULT Resize(double x, double y) override { + if(_inResize) + { + return S_OK; + } + + _inResize = true; + START_COM_CALL; @autoreleasepool @@ -304,13 +313,19 @@ public: y = maxSize.height; } - if(!_shown) + @try { - BaseEvents->Resized(AvnSize{x,y}); + if(!_shown) + { + BaseEvents->Resized(AvnSize{x,y}); + } + + [Window setContentSize:NSSize{x, y}]; + } + @finally + { + _inResize = false; } - - [StandardContainer setFrameSize:NSSize{x,y}]; - [Window setContentSize:NSSize{x, y}]; return S_OK; } @@ -757,6 +772,7 @@ private: } _lastWindowState = state; + _actualWindowState = state; WindowEvents->WindowStateChanged(state); } } @@ -1276,6 +1292,9 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent [_blurBehind setWantsLayer:true]; _blurBehind.hidden = true; + [_blurBehind setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [_content setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [self addSubview:_blurBehind]; [self addSubview:_content]; @@ -1311,9 +1330,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _settingSize = true; [super setFrameSize:newSize]; - [_blurBehind setFrameSize:newSize]; - [_content setFrameSize:newSize]; - auto window = objc_cast([self window]); // TODO get actual titlebar size @@ -1329,6 +1345,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent [_titleBarMaterial setFrame:tbar]; tbar.size.height = height < 1 ? 0 : 1; [_titleBarUnderline setFrame:tbar]; + _settingSize = false; } @@ -2374,11 +2391,12 @@ protected: virtual HRESULT Resize(double x, double y) override { + START_COM_CALL; + @autoreleasepool { if (Window != nullptr) { - [StandardContainer setFrameSize:NSSize{x,y}]; [Window setContentSize:NSSize{x, y}]; [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(lastPositionSet))]; diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs index 5681214222..2c7f34c5be 100644 --- a/src/Avalonia.Base/Collections/AvaloniaList.cs +++ b/src/Avalonia.Base/Collections/AvaloniaList.cs @@ -454,6 +454,28 @@ namespace Avalonia.Collections } } + /// + /// Ensures that the capacity of the list is at least . + /// + /// The capacity. + public void EnsureCapacity(int capacity) + { + // Adapted from List implementation. + var currentCapacity = _inner.Capacity; + + if (currentCapacity < capacity) + { + var newCapacity = currentCapacity == 0 ? 4 : currentCapacity * 2; + + if (newCapacity < capacity) + { + newCapacity = capacity; + } + + _inner.Capacity = newCapacity; + } + } + /// /// Removes an item from the collection. /// @@ -633,24 +655,6 @@ namespace Avalonia.Collections /// Delegate[] INotifyCollectionChangedDebug.GetCollectionChangedSubscribers() => _collectionChanged?.GetInvocationList(); - private void EnsureCapacity(int capacity) - { - // Adapted from List implementation. - var currentCapacity = _inner.Capacity; - - if (currentCapacity < capacity) - { - var newCapacity = currentCapacity == 0 ? 4 : currentCapacity * 2; - - if (newCapacity < capacity) - { - newCapacity = capacity; - } - - _inner.Capacity = newCapacity; - } - } - /// /// Raises the event with an add action. /// diff --git a/src/Avalonia.Controls.DataGrid/Utils/ReflectionHelper.cs b/src/Avalonia.Controls.DataGrid/Utils/ReflectionHelper.cs index 47d1ac1c0b..38e18b57de 100644 --- a/src/Avalonia.Controls.DataGrid/Utils/ReflectionHelper.cs +++ b/src/Avalonia.Controls.DataGrid/Utils/ReflectionHelper.cs @@ -340,10 +340,30 @@ namespace Avalonia.Controls.Utils internal static PropertyInfo GetPropertyOrIndexer(this Type type, string propertyPath, out object[] index) { index = null; + // Return the default value of GetProperty if the first character is not an indexer token. if (string.IsNullOrEmpty(propertyPath) || propertyPath[0] != LeftIndexerToken) { - // Return the default value of GetProperty if the first character is not an indexer token. - return type.GetProperty(propertyPath); + var property = type.GetProperty(propertyPath); + if (property != null) + { + return property; + } + + // GetProperty does not return inherited interface properties, + // so we need to enumerate them manually. + if (type.IsInterface) + { + foreach (var typeInterface in type.GetInterfaces()) + { + property = type.GetProperty(propertyPath); + if (property != null) + { + return property; + } + } + } + + return null; } if (propertyPath.Length < 2 || propertyPath[propertyPath.Length - 1] != RightIndexerToken) diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs index 348922b71d..91141058e4 100644 --- a/src/Avalonia.Controls/Primitives/Thumb.cs +++ b/src/Avalonia.Controls/Primitives/Thumb.cs @@ -56,6 +56,26 @@ namespace Avalonia.Controls.Primitives { } + protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) + { + if (_lastPoint.HasValue) + { + var ev = new VectorEventArgs + { + RoutedEvent = DragCompletedEvent, + Vector = _lastPoint.Value, + }; + + _lastPoint = null; + + RaiseEvent(ev); + } + + PseudoClasses.Remove(":pressed"); + + base.OnPointerCaptureLost(e); + } + protected override void OnPointerMoved(PointerEventArgs e) { if (_lastPoint.HasValue) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs index 24cded1d22..890cbb69bf 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs @@ -9,7 +9,6 @@ using XamlX.Ast; using XamlX.Transform; using XamlX.Transform.Transformers; using XamlX.TypeSystem; - using XamlParseException = XamlX.XamlParseException; namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers @@ -21,6 +20,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (node is XamlAstObjectNode binding && binding.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension)) { var convertedNode = ConvertLongFormPropertiesToBindingExpressionNode(context, binding); + var foundPath = false; if (binding.Arguments.Count > 0 && binding.Arguments[0] is XamlAstTextNode bindingPathText) { @@ -32,9 +32,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers nodes.Insert(nodes.TakeWhile(x => x is BindingExpressionGrammar.ITransformNode).Count(), convertedNode); } - binding.Arguments[0] = new ParsedBindingPathNode(bindingPathText, context.GetAvaloniaTypes().CompiledBindingPath, nodes); + if (nodes.Count == 1 && nodes[0] is BindingExpressionGrammar.EmptyExpressionNode) + { + binding.Arguments.RemoveAt(0); + } + else + { + binding.Arguments[0] = new ParsedBindingPathNode(bindingPathText, context.GetAvaloniaTypes().CompiledBindingPath, nodes); + foundPath = true; + } } - else + + if (!foundPath) { var bindingPathAssignment = binding.Children.OfType() .FirstOrDefault(v => v.Property.GetClrProperty().Name == "Path"); @@ -44,12 +53,19 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers var reader = new CharacterReader(pathValue.Text.AsSpan()); var (nodes, _) = BindingExpressionGrammar.Parse(ref reader); - if (convertedNode != null) + if (nodes.Count == 1 && nodes[0] is BindingExpressionGrammar.EmptyExpressionNode) { - nodes.Insert(nodes.TakeWhile(x => x is BindingExpressionGrammar.ITransformNode).Count(), convertedNode); + bindingPathAssignment.Values.RemoveAt(0); } + else + { + if (convertedNode != null) + { + nodes.Insert(nodes.TakeWhile(x => x is BindingExpressionGrammar.ITransformNode).Count(), convertedNode); + } - bindingPathAssignment.Values[0] = new ParsedBindingPathNode(pathValue, context.GetAvaloniaTypes().CompiledBindingPath, nodes); + bindingPathAssignment.Values[0] = new ParsedBindingPathNode(pathValue, context.GetAvaloniaTypes().CompiledBindingPath, nodes); + } } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 6f549a4ffa..8a61458030 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -938,6 +938,110 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void SupportsEmptyPath() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + var dataContext = new TestDataContext + { + StringProperty = "foobar" + }; + + window.DataContext = dataContext; + + Assert.Equal(typeof(TestDataContext).FullName, textBlock.Text); + } + } + + [Fact] + public void SupportsEmptyPathWithStringFormat() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + var dataContext = new TestDataContext + { + StringProperty = "foobar" + }; + + window.DataContext = dataContext; + + Assert.Equal("bar-" + typeof(TestDataContext).FullName, textBlock.Text); + } + } + + [Fact] + public void SupportsDotPath() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + var dataContext = new TestDataContext + { + StringProperty = "foobar" + }; + + window.DataContext = dataContext; + + Assert.Equal(typeof(TestDataContext).FullName, textBlock.Text); + } + } + + [Fact] + public void SupportsExplicitDotPathWithStringFormat() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + var dataContext = new TestDataContext + { + StringProperty = "foobar" + }; + + window.DataContext = dataContext; + + Assert.Equal("bar-" + typeof(TestDataContext).FullName, textBlock.Text); + } + } + void Throws(string type, Action cb) { try