From 71d1f80d5a12e7dfdd20bbe987d141375f584ba1 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 11 Feb 2020 23:37:37 +0100 Subject: [PATCH 01/18] Benchmark attaching styles. --- .../Styling/StyleAttachBenchmark.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs diff --git a/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs b/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs new file mode 100644 index 0000000000..7bccd65c81 --- /dev/null +++ b/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs @@ -0,0 +1,47 @@ +using System; +using System.Runtime.CompilerServices; +using Avalonia.Controls; +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.Attach(_control, UnitTestApplication.Current); + + styles.Detach(); + } + + public void Dispose() + { + _app.Dispose(); + } + } +} From 2b6a2f4a9bc790e9deaf5832c37ea22312bef419 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 11 Feb 2020 23:46:27 +0100 Subject: [PATCH 02/18] Optimize memory allocations when attaching styles. --- src/Avalonia.Styling/Styling/Setter.cs | 4 +-- src/Avalonia.Styling/Styling/Style.cs | 35 ++++++++++++++++++-------- src/Avalonia.Styling/Styling/Styles.cs | 4 ++- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Styling/Styling/Setter.cs b/src/Avalonia.Styling/Styling/Setter.cs index 9244232ff5..b880ecb01c 100644 --- a/src/Avalonia.Styling/Styling/Setter.cs +++ b/src/Avalonia.Styling/Styling/Setter.cs @@ -78,8 +78,6 @@ namespace Avalonia.Styling { Contract.Requires(control != null); - var description = style?.ToString(); - if (Property == null) { throw new InvalidOperationException("Setter.Property must be set."); @@ -107,6 +105,8 @@ namespace Avalonia.Styling } else { + var description = style?.ToString(); + var activated = new ActivatedValue(activator, value, description); return control.Bind(Property, activated, BindingPriority.StyleTrigger); } diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 99ee8d8563..22db7adfe4 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -116,13 +116,21 @@ namespace Avalonia.Styling if (match.IsMatch) { var controlSubscriptions = GetSubscriptions(control); - - var subs = new CompositeDisposable(Setters.Count + Animations.Count); - if (control is Animatable animatable) + var animatable = control as Animatable; + + var setters = Setters; + var settersCount = setters.Count; + var animations = Animations; + var animationsCount = animations.Count; + + var subs = new CompositeDisposable(settersCount + (animatable != null ? animationsCount : 0) + 1); + + if (animatable != null) { - foreach (var animation in Animations) + for (var i = 0; i < animationsCount; i++) { + var animation = animations[i]; var obsMatch = match.Activator; if (match.Result == SelectorMatchResult.AlwaysThisType || @@ -133,17 +141,19 @@ namespace Avalonia.Styling var sub = animation.Apply(animatable, null, obsMatch); subs.Add(sub); - } + } } - foreach (var setter in Setters) + for (var i = 0; i < settersCount; i++) { + var setter = setters[i]; var sub = setter.Apply(this, control, match.Activator); subs.Add(sub); } + subs.Add(Disposable.Create((subs, Subscriptions) , state => state.Subscriptions.Remove(state.subs))); + controlSubscriptions.Add(subs); - controlSubscriptions.Add(Disposable.Create(() => Subscriptions.Remove(subs))); Subscriptions.Add(subs); } @@ -151,18 +161,23 @@ namespace Avalonia.Styling } else if (control == container) { + var setters = Setters; + var settersCount = setters.Count; + var controlSubscriptions = GetSubscriptions(control); - var subs = new CompositeDisposable(Setters.Count); + var subs = new CompositeDisposable(settersCount + 1); - foreach (var setter in Setters) + for (var i = 0; i < settersCount; i++) { + var setter = setters[i]; var sub = setter.Apply(this, control, null); subs.Add(sub); } + subs.Add(Disposable.Create((subs, Subscriptions), state => state.Subscriptions.Remove(state.subs))); + controlSubscriptions.Add(subs); - controlSubscriptions.Add(Disposable.Create(() => Subscriptions.Remove(subs))); Subscriptions.Add(subs); return true; } diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 0226288998..fd38c39650 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -239,8 +239,10 @@ namespace Avalonia.Styling /// public bool Remove(IStyle item) => _styles.Remove(item); + public AvaloniaList.Enumerator GetEnumerator() => _styles.GetEnumerator(); + /// - public IEnumerator GetEnumerator() => _styles.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _styles.GetEnumerator(); /// IEnumerator IEnumerable.GetEnumerator() => _styles.GetEnumerator(); From c91559548e8e21ad7c90718e465dace3648c9c2f Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Wed, 12 Feb 2020 00:02:32 +0100 Subject: [PATCH 03/18] Remove LINQ from Visual and StyledElement attach/detach funtions. --- src/Avalonia.Styling/StyledElement.cs | 27 +++++++++++++++++++++------ src/Avalonia.Visuals/Visual.cs | 26 ++++++++++++++++++++------ 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index 13cfa905d4..f351e3a93d 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -650,9 +650,12 @@ namespace Avalonia element._dataContextUpdating = true; element.OnDataContextBeginUpdate(); - foreach (var child in element.LogicalChildren) + var logicalChildren = element.LogicalChildren; + var logicalChildrenCount = logicalChildren.Count; + + for (var i = 0; i < logicalChildrenCount; i++) { - if (child is StyledElement s && + if (element.LogicalChildren[i] is StyledElement s && s.InheritanceParent == element && !s.IsSet(DataContextProperty)) { @@ -723,9 +726,15 @@ namespace Avalonia AttachedToLogicalTree?.Invoke(this, e); } - foreach (var child in LogicalChildren.OfType()) + var logicalChildren = LogicalChildren; + var logicalChildrenCount = logicalChildren.Count; + + for (var i = 0; i < logicalChildrenCount; i++) { - child.OnAttachedToLogicalTreeCore(e); + if (logicalChildren[i] is StyledElement child) + { + child.OnAttachedToLogicalTreeCore(e); + } } } @@ -738,9 +747,15 @@ namespace Avalonia OnDetachedFromLogicalTree(e); DetachedFromLogicalTree?.Invoke(this, e); - foreach (var child in LogicalChildren.OfType()) + var logicalChildren = LogicalChildren; + var logicalChildrenCount = logicalChildren.Count; + + for (var i = 0; i < logicalChildrenCount; i++) { - child.OnDetachedFromLogicalTreeCore(e); + if (logicalChildren[i] is StyledElement child) + { + child.OnDetachedFromLogicalTreeCore(e); + } } #if DEBUG diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index ef7a254f86..bd2ab6e614 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -386,11 +386,18 @@ namespace Avalonia AttachedToVisualTree?.Invoke(this, e); InvalidateVisual(); - if (VisualChildren != null) + var visualChildren = VisualChildren; + + if (visualChildren != null) { - foreach (Visual child in VisualChildren.OfType()) + var visualChildrenCount = visualChildren.Count; + + for (var i = 0; i < visualChildrenCount; i++) { - child.OnAttachedToVisualTreeCore(e); + if (visualChildren[i] is Visual child) + { + child.OnAttachedToVisualTreeCore(e); + } } } } @@ -415,11 +422,18 @@ namespace Avalonia DetachedFromVisualTree?.Invoke(this, e); e.Root?.Renderer?.AddDirty(this); - if (VisualChildren != null) + var visualChildren = VisualChildren; + + if (visualChildren != null) { - foreach (Visual child in VisualChildren.OfType()) + var visualChildrenCount = visualChildren.Count; + + for (var i = 0; i < visualChildrenCount; i++) { - child.OnDetachedFromVisualTreeCore(e); + if (visualChildren[i] is Visual child) + { + child.OnDetachedFromVisualTreeCore(e); + } } } } From f16080d00b331c165af114b79417777567c29c49 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 12 Feb 2020 09:36:13 +0100 Subject: [PATCH 04/18] Added failing leak test for #3545. --- tests/Avalonia.LeakTests/ControlTests.cs | 52 ++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 389b3c8df8..3b288dbf66 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -8,8 +8,10 @@ using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Diagnostics; using Avalonia.Layout; +using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; using JetBrains.dotMemoryUnit; @@ -370,6 +372,56 @@ namespace Avalonia.LeakTests } } + [Fact] + public void Control_With_Style_RenderTransform_Is_Freed() + { + // # Issue #3545 + using (Start()) + { + Func run = () => + { + var window = new Window + { + Styles = + { + new Style(x => x.OfType()) + { + Setters = + { + new Setter + { + Property = Visual.RenderTransformProperty, + Value = new RotateTransform(45), + } + } + } + }, + Content = new Canvas() + }; + + window.Show(); + + // Do a layout and make sure that Canvas gets added to visual tree with + // its render transform. + window.LayoutManager.ExecuteInitialLayoutPass(window); + var canvas = Assert.IsType(window.Presenter.Child); + Assert.IsType(canvas.RenderTransform); + + // Clear the content and ensure the Canvas is removed. + window.Content = null; + window.LayoutManager.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); + + return window; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } + } + private IDisposable Start() { return UnitTestApplication.Start(TestServices.StyledWindow); From 11a8c01ef14d821586335a3efc962a25bf6dcac2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 12 Feb 2020 10:21:29 +0100 Subject: [PATCH 05/18] Added failing tests for #3545. `PropertyChanged` is not being fired when binding is disposed. Also change some other unit tests to ensure that priority passed in event args is correct. --- .../AvaloniaObjectTests_Binding.cs | 105 ++++++++++++++++++ .../AvaloniaObjectTests_Direct.cs | 1 + .../AvaloniaObjectTests_SetValue.cs | 1 + 3 files changed, 107 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 4c00d2a1ea..2839fde320 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -132,6 +132,111 @@ namespace Avalonia.Base.UnitTests Assert.Equal("foo", target.GetValue(property)); } + [Fact] + public void Completing_LocalValue_Binding_Raises_PropertyChanged() + { + var target = new Class1(); + var source = new BehaviorSubject>("foo"); + var property = Class1.FooProperty; + var raised = 0; + + target.Bind(property, source); + Assert.Equal("foo", target.GetValue(property)); + + target.PropertyChanged += (s, e) => + { + Assert.Equal(BindingPriority.Unset, e.Priority); + Assert.Equal(property, e.Property); + Assert.Equal("foo", e.OldValue as string); + Assert.Equal("foodefault", e.NewValue as string); + ++raised; + }; + + source.OnCompleted(); + + Assert.Equal("foodefault", target.GetValue(property)); + Assert.Equal(1, raised); + } + + [Fact] + public void Completing_Style_Binding_Raises_PropertyChanged() + { + var target = new Class1(); + var source = new BehaviorSubject>("foo"); + var property = Class1.FooProperty; + var raised = 0; + + target.Bind(property, source, BindingPriority.Style); + Assert.Equal("foo", target.GetValue(property)); + + target.PropertyChanged += (s, e) => + { + Assert.Equal(BindingPriority.Unset, e.Priority); + Assert.Equal(property, e.Property); + Assert.Equal("foo", e.OldValue as string); + Assert.Equal("foodefault", e.NewValue as string); + ++raised; + }; + + source.OnCompleted(); + + Assert.Equal("foodefault", target.GetValue(property)); + Assert.Equal(1, raised); + } + + [Fact] + public void Completing_LocalValue_Binding_With_Style_Binding_Raises_PropertyChanged() + { + var target = new Class1(); + var source = new BehaviorSubject>("foo"); + var property = Class1.FooProperty; + var raised = 0; + + target.Bind(property, new BehaviorSubject("bar"), BindingPriority.Style); + target.Bind(property, source); + Assert.Equal("foo", target.GetValue(property)); + + target.PropertyChanged += (s, e) => + { + Assert.Equal(BindingPriority.Style, e.Priority); + Assert.Equal(property, e.Property); + Assert.Equal("foo", e.OldValue as string); + Assert.Equal("bar", e.NewValue as string); + ++raised; + }; + + source.OnCompleted(); + + Assert.Equal("bar", target.GetValue(property)); + Assert.Equal(1, raised); + } + + [Fact] + public void Disposing_LocalValue_Binding_Raises_PropertyChanged() + { + var target = new Class1(); + var source = new BehaviorSubject>("foo"); + var property = Class1.FooProperty; + var raised = 0; + + var sub = target.Bind(property, source); + Assert.Equal("foo", target.GetValue(property)); + + target.PropertyChanged += (s, e) => + { + Assert.Equal(BindingPriority.Unset, e.Priority); + Assert.Equal(property, e.Property); + Assert.Equal("foo", e.OldValue as string); + Assert.Equal("foodefault", e.NewValue as string); + ++raised; + }; + + sub.Dispose(); + + Assert.Equal("foodefault", target.GetValue(property)); + Assert.Equal(1, raised); + } + [Fact] public void Setting_Style_Value_Overrides_Binding_Permanently() { diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index b1a5b5ae92..ca17afb94f 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -188,6 +188,7 @@ namespace Avalonia.Base.UnitTests target.PropertyChanged += (s, e) => { Assert.Same(target, s); + Assert.Equal(BindingPriority.LocalValue, e.Priority); Assert.Equal(Class1.FooProperty, e.Property); Assert.Equal("newvalue", (string)e.OldValue); Assert.Equal("unset", (string)e.NewValue); diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs index 40631d04cf..98305cc110 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs @@ -30,6 +30,7 @@ namespace Avalonia.Base.UnitTests target.PropertyChanged += (s, e) => { Assert.Same(target, s); + Assert.Equal(BindingPriority.Unset, e.Priority); Assert.Equal(Class1.FooProperty, e.Property); Assert.Equal("newvalue", (string)e.OldValue); Assert.Equal("foodefault", (string)e.NewValue); From 187e45d02134af45dbc65823472b73d3ee3116d1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 12 Feb 2020 10:22:57 +0100 Subject: [PATCH 06/18] Make ClearLocalValue notify with correct priority. --- src/Avalonia.Base/ValueStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index 58ebc48652..125f404e2c 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -148,7 +148,7 @@ namespace Avalonia _values.Remove(property); _sink.ValueChanged( property, - BindingPriority.LocalValue, + BindingPriority.Unset, old, BindingValue.Unset); } From edc8bece4ea2cf8b8296d1e88ed984f8caebb091 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 12 Feb 2020 10:28:01 +0100 Subject: [PATCH 07/18] Raised property changed when binding completes. Fixes #3545 --- src/Avalonia.Base/AvaloniaObject.cs | 8 +++++++- src/Avalonia.Base/PropertyStore/BindingEntry.cs | 4 ++-- src/Avalonia.Base/PropertyStore/IValueSink.cs | 5 ++++- src/Avalonia.Base/PropertyStore/PriorityValue.cs | 5 ++++- src/Avalonia.Base/ValueStore.cs | 6 +++++- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index ddc3d8d081..b0ff591682 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -478,7 +478,13 @@ namespace Avalonia } } - void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry) { } + void IValueSink.Completed( + StyledPropertyBase property, + IPriorityValueEntry entry, + Optional oldValue) + { + ((IValueSink)this).ValueChanged(property, BindingPriority.Unset, oldValue, default); + } /// /// Called for each inherited property when the changes. diff --git a/src/Avalonia.Base/PropertyStore/BindingEntry.cs b/src/Avalonia.Base/PropertyStore/BindingEntry.cs index 09a0f169df..3249b31d66 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntry.cs @@ -48,10 +48,10 @@ namespace Avalonia.PropertyStore { _subscription?.Dispose(); _subscription = null; - _sink.Completed(Property, this); + _sink.Completed(Property, this, Value); } - public void OnCompleted() => _sink.Completed(Property, this); + public void OnCompleted() => _sink.Completed(Property, this, Value); public void OnError(Exception error) { diff --git a/src/Avalonia.Base/PropertyStore/IValueSink.cs b/src/Avalonia.Base/PropertyStore/IValueSink.cs index 223b0058c1..9012a985ac 100644 --- a/src/Avalonia.Base/PropertyStore/IValueSink.cs +++ b/src/Avalonia.Base/PropertyStore/IValueSink.cs @@ -15,6 +15,9 @@ namespace Avalonia.PropertyStore Optional oldValue, BindingValue newValue); - void Completed(AvaloniaProperty property, IPriorityValueEntry entry); + void Completed( + StyledPropertyBase property, + IPriorityValueEntry entry, + Optional oldValue); } } diff --git a/src/Avalonia.Base/PropertyStore/PriorityValue.cs b/src/Avalonia.Base/PropertyStore/PriorityValue.cs index 2785dc6840..4ef8f650fa 100644 --- a/src/Avalonia.Base/PropertyStore/PriorityValue.cs +++ b/src/Avalonia.Base/PropertyStore/PriorityValue.cs @@ -117,7 +117,10 @@ namespace Avalonia.PropertyStore UpdateEffectiveValue(); } - void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry) + void IValueSink.Completed( + StyledPropertyBase property, + IPriorityValueEntry entry, + Optional oldValue) { _entries.Remove((IPriorityValueEntry)entry); UpdateEffectiveValue(); diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index 125f404e2c..22cded565a 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -190,13 +190,17 @@ namespace Avalonia _sink.ValueChanged(property, priority, oldValue, newValue); } - void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry) + void IValueSink.Completed( + StyledPropertyBase property, + IPriorityValueEntry entry, + Optional oldValue) { if (_values.TryGetValue(property, out var slot)) { if (slot == entry) { _values.Remove(property); + _sink.Completed(property, entry, oldValue); } } } From 9763faec3a71a7bf2ebb1a7a83f950574f681b2d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 14 Feb 2020 09:39:37 +0100 Subject: [PATCH 08/18] Added failing test for #3569. And one passing test too. --- .../WindowTests.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 0508edd92f..81699b075c 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -341,11 +341,62 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Child_Should_Be_Measured_With_Width_And_Height_If_SizeToContent_Is_Manual() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var child = new ChildControl(); + var target = new Window + { + Width = 100, + Height = 50, + SizeToContent = SizeToContent.Manual, + Content = child + }; + + target.Show(); + + Assert.Equal(new Size(100, 50), child.MeasureSize); + } + } + + [Fact] + public void Child_Should_Be_Measured_With_Infinity_If_SizeToContent_Is_WidthAndHeight() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var child = new ChildControl(); + var target = new Window + { + Width = 100, + Height = 50, + SizeToContent = SizeToContent.WidthAndHeight, + Content = child + }; + + target.Show(); + + Assert.Equal(Size.Infinity, child.MeasureSize); + } + } + private IWindowImpl CreateImpl(Mock renderer) { return Mock.Of(x => x.Scaling == 1 && x.CreateRenderer(It.IsAny()) == renderer.Object); } + + private class ChildControl : Control + { + public Size MeasureSize { get; private set; } + + protected override Size MeasureOverride(Size availableSize) + { + MeasureSize = availableSize; + return base.MeasureOverride(availableSize); + } + } } } From 195c7ba6ea5bfc40a0ded905e280e605f657f927 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 14 Feb 2020 09:40:55 +0100 Subject: [PATCH 09/18] Don't ignore available size in window measure. Fixes #3569. --- src/Avalonia.Controls/Window.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 0c37858223..6554237b3a 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -529,7 +529,7 @@ namespace Avalonia.Controls { var sizeToContent = SizeToContent; var clientSize = ClientSize; - Size constraint = clientSize; + var constraint = availableSize; if ((sizeToContent & SizeToContent.Width) != 0) { From 5ad2b362df759cac7c47290dca138001ba505571 Mon Sep 17 00:00:00 2001 From: Symbai <14368203+Symbai@users.noreply.github.com> Date: Fri, 14 Feb 2020 16:46:32 +0100 Subject: [PATCH 10/18] Update TextBox.cs --- src/Avalonia.Controls/TextBox.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index b561a3423f..387af58255 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -316,8 +316,7 @@ namespace Avalonia.Controls !AcceptsReturn && Text?.Length > 0) { - SelectionStart = 0; - SelectionEnd = Text.Length; + SelectAll(); } else { @@ -673,8 +672,7 @@ namespace Avalonia.Controls SelectionEnd = StringUtils.NextWord(text, index); break; case 3: - SelectionStart = 0; - SelectionEnd = text.Length; + SelectAll(); break; } } @@ -896,7 +894,10 @@ namespace Avalonia.Controls CaretIndex = caretIndex; } - private void SelectAll() + /// + /// Select all text in the TextBox + /// + public void SelectAll() { SelectionStart = 0; SelectionEnd = Text?.Length ?? 0; From f4fe0b126cce57a37cfd32cf88515c54c1ced7ab Mon Sep 17 00:00:00 2001 From: Symbai <14368203+Symbai@users.noreply.github.com> Date: Fri, 14 Feb 2020 18:40:27 +0100 Subject: [PATCH 11/18] init --- src/Avalonia.Controls/TextBox.cs | 24 ++++++++++++++++++++++++ src/Avalonia.Themes.Default/TextBox.xaml | 5 +++++ 2 files changed, 29 insertions(+) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index b561a3423f..e38cf7955f 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -14,6 +14,7 @@ using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Metadata; using Avalonia.Data; +using Avalonia.Layout; using Avalonia.Utilities; namespace Avalonia.Controls @@ -72,6 +73,18 @@ namespace Avalonia.Controls public static readonly StyledProperty TextAlignmentProperty = TextBlock.TextAlignmentProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty HorizontalContentAlignmentProperty = + AvaloniaProperty.Register(nameof(HorizontalContentAlignment)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty VerticalContentAlignmentProperty = + AvaloniaProperty.Register(nameof(VerticalContentAlignment)); + public static readonly StyledProperty TextWrappingProperty = TextBlock.TextWrappingProperty.AddOwner(); @@ -262,6 +275,17 @@ namespace Avalonia.Controls } } + public HorizontalAlignment HorizontalContentAlignment + { + get { return GetValue(HorizontalContentAlignmentProperty); } + set { SetValue(HorizontalContentAlignmentProperty, value); } + } + public VerticalAlignment VerticalContentAlignment + { + get { return GetValue(VerticalContentAlignmentProperty); } + set { SetValue(VerticalContentAlignmentProperty, value); } + } + public TextAlignment TextAlignment { get { return GetValue(TextAlignmentProperty); } diff --git a/src/Avalonia.Themes.Default/TextBox.xaml b/src/Avalonia.Themes.Default/TextBox.xaml index 423d19da14..53c262c4fe 100644 --- a/src/Avalonia.Themes.Default/TextBox.xaml +++ b/src/Avalonia.Themes.Default/TextBox.xaml @@ -15,6 +15,7 @@ Date: Fri, 14 Feb 2020 18:48:52 +0100 Subject: [PATCH 12/18] Include vertical alignment of floating watermark --- src/Avalonia.Themes.Default/TextBox.xaml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Themes.Default/TextBox.xaml b/src/Avalonia.Themes.Default/TextBox.xaml index 53c262c4fe..2c4cafde26 100644 --- a/src/Avalonia.Themes.Default/TextBox.xaml +++ b/src/Avalonia.Themes.Default/TextBox.xaml @@ -12,10 +12,11 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> - + Date: Fri, 14 Feb 2020 22:25:10 +0100 Subject: [PATCH 13/18] inherit from ContentControl --- src/Avalonia.Controls/TextBox.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index e38cf7955f..dfbdcd16d8 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -77,13 +77,13 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly StyledProperty HorizontalContentAlignmentProperty = - AvaloniaProperty.Register(nameof(HorizontalContentAlignment)); + ContentControl.HorizontalContentAlignmentProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty VerticalContentAlignmentProperty = - AvaloniaProperty.Register(nameof(VerticalContentAlignment)); + ContentControl.VerticalContentAlignmentProperty.AddOwner(); public static readonly StyledProperty TextWrappingProperty = TextBlock.TextWrappingProperty.AddOwner(); From f79b58cc55e2ab172d1b417aab99edfa01e81509 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 14 Feb 2020 23:13:32 +0100 Subject: [PATCH 14/18] Added failing test possibly for #3466. Not sure if this is the cause of #3466, but it's definitely wrong. --- .../AvaloniaObjectTests_SetValue.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs index 98305cc110..4b477287e8 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs @@ -240,6 +240,17 @@ namespace Avalonia.Base.UnitTests Assert.Equal("two", target.GetValue(Class1.FooProperty)); } + [Fact] + public void SetValue_Animation_Overrides_LocalValue() + { + Class1 target = new Class1(); + + target.SetValue(Class1.FooProperty, "one", BindingPriority.LocalValue); + Assert.Equal("one", target.GetValue(Class1.FooProperty)); + target.SetValue(Class1.FooProperty, "two", BindingPriority.Animation); + Assert.Equal("two", target.GetValue(Class1.FooProperty)); + } + [Fact] public void Setting_UnsetValue_Reverts_To_Default_Value() { From aec9c6e5e2efb6e98c5fa7e132f7c754aa568ad2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 14 Feb 2020 23:14:40 +0100 Subject: [PATCH 15/18] Make animation value override local value. This line was missing. Might be the cause of #3466. --- src/Avalonia.Base/ValueStore.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index 22cded565a..e9118af9f1 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -232,6 +232,7 @@ namespace Avalonia else { var priorityValue = new PriorityValue(_owner, property, this, l); + priorityValue.SetValue(value, priority); _values.SetValue(property, priorityValue); } } From dc8881c846cc1d7853735a7521cbd697319b2624 Mon Sep 17 00:00:00 2001 From: Symbai <14368203+Symbai@users.noreply.github.com> Date: Fri, 14 Feb 2020 23:37:11 +0100 Subject: [PATCH 16/18] Update TextBox.cs --- src/Avalonia.Controls/TextBox.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index dfbdcd16d8..d20d8a3647 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -275,11 +275,17 @@ namespace Avalonia.Controls } } + /// + /// Gets or sets the horizontal alignment of the content within the control. + /// public HorizontalAlignment HorizontalContentAlignment { get { return GetValue(HorizontalContentAlignmentProperty); } set { SetValue(HorizontalContentAlignmentProperty, value); } } + /// + /// Gets or sets the vertical alignment of the content within the control. + /// public VerticalAlignment VerticalContentAlignment { get { return GetValue(VerticalContentAlignmentProperty); } From 994def4642ef9259a1718f35694481e6ffe3ca45 Mon Sep 17 00:00:00 2001 From: Symbai <14368203+Symbai@users.noreply.github.com> Date: Sat, 15 Feb 2020 00:09:33 +0100 Subject: [PATCH 17/18] Update TextBox.cs --- src/Avalonia.Controls/TextBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index d20d8a3647..437b24dd9e 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -283,6 +283,7 @@ namespace Avalonia.Controls get { return GetValue(HorizontalContentAlignmentProperty); } set { SetValue(HorizontalContentAlignmentProperty, value); } } + /// /// Gets or sets the vertical alignment of the content within the control. /// From 97bcb443ffe20c1464eb89f691978edaec0e0baa Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 16 Feb 2020 12:36:55 -0300 Subject: [PATCH 18/18] dont crash when closing window on OSX. --- src/Avalonia.Native/WindowImplBase.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 64cea1c430..331f083e8b 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -162,9 +162,12 @@ namespace Avalonia.Native void IAvnWindowBaseEvents.Resized(AvnSize size) { - var s = new Size(size.Width, size.Height); - _parent._savedLogicalSize = s; - _parent.Resized?.Invoke(s); + if (_parent._native != null) + { + var s = new Size(size.Width, size.Height); + _parent._savedLogicalSize = s; + _parent.Resized?.Invoke(s); + } } void IAvnWindowBaseEvents.PositionChanged(AvnPoint position) @@ -317,7 +320,7 @@ namespace Avalonia.Native _native.SetTopMost(value); } - public double Scaling => _native.GetScaling(); + public double Scaling => _native?.GetScaling() ?? 1; public Action Deactivated { get; set; } public Action Activated { get; set; }