From a26059630f19e5ec8ceb5d65aff29193ee74a036 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Feb 2022 14:57:08 +0100 Subject: [PATCH 001/240] Add failing test for control template value priority. Add tests to check that template values are assigned with Style priority and move other XAML control template tests alongside it in a new test class. --- .../Xaml/BasicTests.cs | 66 +------- .../Xaml/ControlTemplateTests.cs | 143 ++++++++++++++++++ 2 files changed, 144 insertions(+), 65 deletions(-) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index f20faa2287..16685c5d2d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -281,70 +281,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal(expected4, grid.RowDefinitions[3].Height); } - [Fact] - public void ControlTemplate_With_Nested_Child_Is_Operational() - { - var xaml = @" - - - - - -"; - var template = AvaloniaRuntimeXamlLoader.Parse(xaml); - - var parent = (ContentControl)template.Build(new ContentControl()).Control; - - Assert.Equal("parent", parent.Name); - - var child = parent.Content as ContentControl; - - Assert.NotNull(child); - - Assert.Equal("child", child.Name); - } - - [Fact] - public void ControlTemplate_With_TargetType_Is_Operational() - { - var xaml = @" - - - -"; - var template = AvaloniaRuntimeXamlLoader.Parse(xaml); - - Assert.Equal(typeof(ContentControl), template.TargetType); - - Assert.IsType(typeof(ContentPresenter), template.Build(new ContentControl()).Control); - } - - [Fact] - public void ControlTemplate_With_Panel_Children_Are_Added() - { - var xaml = @" - - - - - - -"; - var template = AvaloniaRuntimeXamlLoader.Parse(xaml); - - var panel = (Panel)template.Build(new ContentControl()).Control; - - Assert.Equal(2, panel.Children.Count); - - var foo = panel.Children[0]; - var bar = panel.Children[1]; - - Assert.Equal("Foo", foo.Name); - Assert.Equal("Bar", bar.Name); - } - [Fact] public void Named_x_Control_Is_Added_To_NameScope_Simple() { @@ -361,7 +297,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } [Fact] - public void Standart_TypeConverter_Is_Used() + public void Standard_TypeConverter_Is_Used() { var xaml = @""; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs new file mode 100644 index 0000000000..fbb16d9f2e --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs @@ -0,0 +1,143 @@ +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Data; +using Avalonia.Diagnostics; +using Avalonia.Markup.Xaml.Templates; +using Avalonia.Media; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml +{ + public class ControlTemplateTests : XamlTestBase + { + [Fact] + public void Inline_ControlTemplate_Values_Are_Set_With_Style_Priority() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var button = (Button)window.Content; + + window.ApplyTemplate(); + button.ApplyTemplate(); + + var presenter = (ContentPresenter)button.Presenter; + Assert.Equal(Brushes.Red, presenter.Background); + + var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty); + Assert.Equal(BindingPriority.Style, diagnostic.Priority); + } + } + + [Fact] + public void Style_ControlTemplate_Values_Are_Set_With_Style_Priority() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var button = (Button)window.Content; + + window.ApplyTemplate(); + button.ApplyTemplate(); + + var presenter = (ContentPresenter)button.Presenter; + Assert.Equal(Dock.Top, DockPanel.GetDock(presenter)); + + var diagnostic = presenter.GetDiagnostic(DockPanel.DockProperty); + Assert.Equal(BindingPriority.Style, diagnostic.Priority); + } + } + + [Fact] + public void ControlTemplate_TemplateBindings_Are_Set_With_TemplatedParent_Priority() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var button = (Button)window.Content; + + window.ApplyTemplate(); + button.ApplyTemplate(); + + var presenter = (ContentPresenter)button.Presenter; + Assert.Equal("Foo", presenter.Content); + + var diagnostic = presenter.GetDiagnostic(ContentPresenter.ContentProperty); + Assert.Equal(BindingPriority.TemplatedParent, diagnostic.Priority); + } + } + [Fact] public void ControlTemplate_With_Nested_Child_Is_Operational() { From f7ff0a02893396f5ed28a033005d9907b97af096 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 20 Feb 2022 23:48:49 +0100 Subject: [PATCH 006/240] More failing tests for ControlTemplate resources. --- .../Xaml/ControlTemplateTests.cs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs index f74aaaed76..2c38a82031 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs @@ -107,6 +107,74 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } } + [Fact] + public void ControlTemplate_StaticResources_Are_Set_With_Style_Priority() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + Red + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var button = (Button)window.Content; + + window.ApplyTemplate(); + button.ApplyTemplate(); + + var presenter = (ContentPresenter)button.Presenter; + Assert.Equal(Brushes.Red, presenter.Background); + + var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty); + Assert.Equal(BindingPriority.Style, diagnostic.Priority); + } + } + + [Fact] + public void ControlTemplate_DynamicResources_Are_Set_With_Style_Priority() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + Red + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var button = (Button)window.Content; + + window.ApplyTemplate(); + button.ApplyTemplate(); + + var presenter = (ContentPresenter)button.Presenter; + Assert.Equal(Brushes.Red, presenter.Background); + + var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty); + Assert.Equal(BindingPriority.Style, diagnostic.Priority); + } + } + [Fact] public void ControlTemplate_TemplateBindings_Are_Set_With_TemplatedParent_Priority() { From cb7488baa7ed96fedd0308f8363c7003f4484893 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 21 Feb 2022 22:54:58 +0100 Subject: [PATCH 007/240] Use Style priority for StaticResource in ControlTemplate. --- ...amlIlControlTemplatePriorityTransformer.cs | 33 +++++++++++-------- .../XamlIlAvaloniaPropertyHelper.cs | 32 ++++++++++++++++-- .../Avalonia.Markup.Xaml.Loader/xamlil.github | 2 +- 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs index eede21396d..e0090eb2e8 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs @@ -18,26 +18,31 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers // The node is a candidate for transformation if: // - It's a property assignment to an Avalonia property // - There's a ControlTemplate ancestor - // - There's just a direct call setter available + // - The property has a single value if (node is XamlPropertyAssignmentNode prop && prop.Property is XamlIlAvaloniaProperty avaloniaProperty && context.ParentNodes().Any(IsControlTemplate) && - prop.PossibleSetters.Count == 1 && - prop.PossibleSetters[0] is XamlDirectCallPropertySetter) + prop.Values.Count == 1) { - // Check if there are any setters on the property which accept a binding priority - - // this filters the candidates down to styled and attached properties. If so, then - // use this setter with BindingPriority.Style. - var setPriorityValueSetter = - avaloniaProperty.Setters.FirstOrDefault(x => x.Parameters[0] == bindingPriorityType); - - if(setPriorityValueSetter != null - && prop.Values.Count == 1 - && setPriorityValueSetter.Parameters[1].IsAssignableFrom(prop.Values[0].Type.GetClrType())) + var priorityValueSetters = new List(); + + // Iterate through the possible setters, trying to find a setter on the property + // which has a BindingPriority parameter followed by the parameter of the existing + // setter. + foreach (var setter in prop.PossibleSetters) + { + var s = avaloniaProperty.Setters.FirstOrDefault(x => + x.Parameters[0] == bindingPriorityType && + x.Parameters[1] == setter.Parameters[0]); + if (s != null) + priorityValueSetters.Add(s); + } + + // If any BindingPriority setters were found, use those. + if (priorityValueSetters.Count > 0) { - prop.PossibleSetters = new List { setPriorityValueSetter }; + prop.PossibleSetters = priorityValueSetters; prop.Values.Insert(0, new XamlConstantNode(node, bindingPriorityType, (int)BindingPriority.Style)); - return node; } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs index bb439b8816..5c7a80e680 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs @@ -185,9 +185,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions AvaloniaXamlIlWellKnownTypes types) :base(original, original.Name, original.DeclaringType, original.Getter, original.Setters) { + var assignBinding = original.CustomAttributes.Any(ca => ca.Type.Equals(types.AssignBindingAttribute)); + AvaloniaProperty = field; CustomAttributes = original.CustomAttributes; - if (!original.CustomAttributes.Any(ca => ca.Type.Equals(types.AssignBindingAttribute))) + if (!assignBinding) Setters.Insert(0, new BindingSetter(types, original.DeclaringType, field)); // Styled and attached properties can be set with a BindingPriority when they're @@ -196,7 +198,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions field.FieldType.GenericTypeDefinition == types.AvaloniaAttachedPropertyT) { var propertyType = field.FieldType.GenericArguments[0]; - Setters.Insert(1, new SetValueWithPrioritySetter(types, original.DeclaringType, field, propertyType)); + Setters.Insert(0, new SetValueWithPrioritySetter(types, original.DeclaringType, field, propertyType)); + if (!assignBinding) + Setters.Insert(1, new BindingWithPrioritySetter(types, original.DeclaringType, field)); } Setters.Insert(0, new UnsetValueSetter(types, original.DeclaringType, field)); @@ -249,6 +253,30 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } } + class BindingWithPrioritySetter : AvaloniaPropertyCustomSetter + { + public BindingWithPrioritySetter(AvaloniaXamlIlWellKnownTypes types, + IXamlType declaringType, + IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty) + { + Parameters = new[] { types.BindingPriority, types.IBinding }; + } + + public override void Emit(IXamlILEmitter emitter) + { + using (var bloc = emitter.LocalsPool.GetLocal(Types.IBinding)) + using (var priorityLocal = emitter.LocalsPool.GetLocal(Types.Int)) + emitter + .Stloc(bloc.Local) + .Stloc(priorityLocal.Local) + .Ldsfld(AvaloniaProperty) + .Ldloc(bloc.Local) + // TODO: provide anchor? + .Ldnull(); + emitter.EmitCall(Types.AvaloniaObjectBindMethod, true); + } + } + class SetValueWithPrioritySetter : AvaloniaPropertyCustomSetter { public SetValueWithPrioritySetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, IXamlField avaloniaProperty, diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github index e0cfcd0ef6..a4897d581c 160000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github @@ -1 +1 @@ -Subproject commit e0cfcd0ef687e613ef8905e8d0891974b68c565d +Subproject commit a4897d581c7f155543102f0d744a47863e1fbfea From a142b7ea04578e2b4cc5e521a38288fe9b3cd790 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Feb 2022 23:14:46 +0100 Subject: [PATCH 008/240] Detect control template from markup extensions. Allow markup extensions to detect whether they're in a control template and use that info in `DynamicResourceExtension` to use `Style` priority. --- .../Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj | 1 + src/Markup/Avalonia.Markup.Xaml/Extensions.cs | 4 ++-- .../MarkupExtensions/DynamicResourceExtension.cs | 8 ++++++-- .../Runtime/IAvaloniaXamlIlControlTemplateProvider.cs | 8 ++++++++ .../XamlIl/Runtime/XamlIlRuntimeHelpers.cs | 5 ++++- 5 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/IAvaloniaXamlIlControlTemplateProvider.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 86132c5d27..4d9e216916 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -46,6 +46,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/Extensions.cs b/src/Markup/Avalonia.Markup.Xaml/Extensions.cs index 263750c316..d937a83010 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Extensions.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Extensions.cs @@ -24,10 +24,10 @@ namespace Avalonia.Markup.Xaml public static IEnumerable GetParents(this IServiceProvider sp) { return sp.GetService().Parents.OfType(); - - } + public static bool IsInControlTemplate(this IServiceProvider sp) => sp.GetService() != null; + public static Type ResolveType(this IServiceProvider ctx, string namespacePrefix, string type) { var tr = ctx.GetService(); diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index 087611bd59..65e38508bc 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -11,6 +11,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions public class DynamicResourceExtension : IBinding { private object? _anchor; + private BindingPriority _priority; public DynamicResourceExtension() { @@ -25,6 +26,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions public IBinding ProvideValue(IServiceProvider serviceProvider) { + if (serviceProvider.IsInControlTemplate()) + _priority = BindingPriority.Style; + var provideTarget = serviceProvider.GetService(); if (!(provideTarget.TargetObject is IStyledElement)) @@ -53,12 +57,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions if (control != null) { var source = control.GetResourceObservable(ResourceKey, GetConverter(targetProperty)); - return InstancedBinding.OneWay(source); + return InstancedBinding.OneWay(source, _priority); } else if (_anchor is IResourceProvider resourceProvider) { var source = resourceProvider.GetResourceObservable(ResourceKey, GetConverter(targetProperty)); - return InstancedBinding.OneWay(source); + return InstancedBinding.OneWay(source, _priority); } return null; diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/IAvaloniaXamlIlControlTemplateProvider.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/IAvaloniaXamlIlControlTemplateProvider.cs new file mode 100644 index 0000000000..ed3f5bfdff --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/IAvaloniaXamlIlControlTemplateProvider.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Avalonia.Markup.Xaml.XamlIl.Runtime +{ + public interface IAvaloniaXamlIlControlTemplateProvider + { + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs index c48f386ffd..a0e7fd7dcf 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs @@ -42,7 +42,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime class DeferredParentServiceProvider : IAvaloniaXamlIlParentStackProvider, IServiceProvider, - IRootObjectProvider + IRootObjectProvider, + IAvaloniaXamlIlControlTemplateProvider { private readonly IServiceProvider _parentProvider; private readonly List _parentResourceNodes; @@ -75,6 +76,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime return this; if (serviceType == typeof(IRootObjectProvider)) return this; + if (serviceType == typeof(IAvaloniaXamlIlControlTemplateProvider)) + return this; return _parentProvider?.GetService(serviceType); } From beaf956af72b4ad38a56f92b6d9ec048fedb2815 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Feb 2022 23:23:15 +0100 Subject: [PATCH 009/240] Use TemplatedParent priority in ControlTemplates. --- ...AvaloniaXamlIlControlTemplatePriorityTransformer.cs | 2 +- .../MarkupExtensions/DynamicResourceExtension.cs | 2 +- .../Xaml/ControlTemplateTests.cs | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs index e0090eb2e8..6cab68e756 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs @@ -42,7 +42,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (priorityValueSetters.Count > 0) { prop.PossibleSetters = priorityValueSetters; - prop.Values.Insert(0, new XamlConstantNode(node, bindingPriorityType, (int)BindingPriority.Style)); + prop.Values.Insert(0, new XamlConstantNode(node, bindingPriorityType, (int)BindingPriority.TemplatedParent)); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index 65e38508bc..f13ecab4e1 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -27,7 +27,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions public IBinding ProvideValue(IServiceProvider serviceProvider) { if (serviceProvider.IsInControlTemplate()) - _priority = BindingPriority.Style; + _priority = BindingPriority.TemplatedParent; var provideTarget = serviceProvider.GetService(); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs index 2c38a82031..2d6ed1dc62 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs @@ -38,7 +38,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal(Brushes.Red, presenter.Background); var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty); - Assert.Equal(BindingPriority.Style, diagnostic.Priority); + Assert.Equal(BindingPriority.TemplatedParent, diagnostic.Priority); } } @@ -72,7 +72,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal(Brushes.Red, presenter.Background); var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty); - Assert.Equal(BindingPriority.Style, diagnostic.Priority); + Assert.Equal(BindingPriority.TemplatedParent, diagnostic.Priority); } } @@ -103,7 +103,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal(Dock.Top, DockPanel.GetDock(presenter)); var diagnostic = presenter.GetDiagnostic(DockPanel.DockProperty); - Assert.Equal(BindingPriority.Style, diagnostic.Priority); + Assert.Equal(BindingPriority.TemplatedParent, diagnostic.Priority); } } @@ -137,7 +137,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal(Brushes.Red, presenter.Background); var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty); - Assert.Equal(BindingPriority.Style, diagnostic.Priority); + Assert.Equal(BindingPriority.TemplatedParent, diagnostic.Priority); } } @@ -171,7 +171,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal(Brushes.Red, presenter.Background); var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty); - Assert.Equal(BindingPriority.Style, diagnostic.Priority); + Assert.Equal(BindingPriority.TemplatedParent, diagnostic.Priority); } } From c02439aaaf5c1984f8aae75c777173c775c89257 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 30 May 2022 17:10:09 +0200 Subject: [PATCH 010/240] Refactored most of Style into StyleBase. Ready for `ControlTheme` class, which is a style without a selector. --- src/Avalonia.Base/Styling/Style.cs | 148 ++------------------- src/Avalonia.Base/Styling/StyleBase.cs | 137 +++++++++++++++++++ src/Avalonia.Base/Styling/StyleChildren.cs | 10 +- 3 files changed, 151 insertions(+), 144 deletions(-) create mode 100644 src/Avalonia.Base/Styling/StyleBase.cs diff --git a/src/Avalonia.Base/Styling/Style.cs b/src/Avalonia.Base/Styling/Style.cs index 000e588bad..c85c85fe21 100644 --- a/src/Avalonia.Base/Styling/Style.cs +++ b/src/Avalonia.Base/Styling/Style.cs @@ -1,23 +1,12 @@ using System; -using System.Collections.Generic; -using Avalonia.Animation; -using Avalonia.Controls; -using Avalonia.Metadata; namespace Avalonia.Styling { /// /// Defines a style. /// - public class Style : AvaloniaObject, IStyle, IResourceProvider + public class Style : StyleBase { - private IResourceHost? _owner; - private StyleChildren? _children; - private IResourceDictionary? _resources; - private List? _setters; - private List? _animations; - private StyleCache? _childCache; - /// /// Initializes a new instance of the class. /// @@ -34,114 +23,11 @@ namespace Avalonia.Styling Selector = selector(null); } - /// - /// Gets the children of the style. - /// - public IList Children => _children ??= new(this); - - /// - /// Gets the or Application that hosts the style. - /// - public IResourceHost? Owner - { - get => _owner; - private set - { - if (_owner != value) - { - _owner = value; - OwnerChanged?.Invoke(this, EventArgs.Empty); - } - } - } - - /// - /// Gets the parent style if this style is hosted in a collection. - /// - public Style? Parent { get; private set; } - - /// - /// Gets or sets a dictionary of style resources. - /// - public IResourceDictionary Resources - { - get => _resources ?? (Resources = new ResourceDictionary()); - set - { - value = value ?? throw new ArgumentNullException(nameof(value)); - - var hadResources = _resources?.HasResources ?? false; - - _resources = value; - - if (Owner is object) - { - _resources.AddOwner(Owner); - - if (hadResources || _resources.HasResources) - { - Owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); - } - } - } - } - /// /// Gets or sets the style's selector. /// public Selector? Selector { get; set; } - /// - /// Gets the style's setters. - /// - public IList Setters => _setters ??= new List(); - - /// - /// Gets the style's animations. - /// - public IList Animations => _animations ??= new List(); - - bool IResourceNode.HasResources => _resources?.Count > 0; - IReadOnlyList IStyle.Children => (IReadOnlyList?)_children ?? Array.Empty(); - - public event EventHandler? OwnerChanged; - - public void Add(ISetter setter) => Setters.Add(setter); - public void Add(IStyle style) => Children.Add(style); - - public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) - { - target = target ?? throw new ArgumentNullException(nameof(target)); - - var match = Selector is object ? Selector.Match(target, Parent) : - target == host ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; - - if (match.IsMatch && (_setters is object || _animations is object)) - { - var instance = new StyleInstance(this, target, _setters, _animations, match.Activator); - target.StyleApplied(instance); - instance.Start(); - } - - var result = match.Result; - - if (_children is not null) - { - _childCache ??= new StyleCache(); - var childResult = _childCache.TryAttach(_children, target, host); - if (childResult > result) - result = childResult; - } - - return result; - } - - public bool TryGetResource(object key, out object? result) - { - result = null; - return _resources?.TryGetResource(key, out result) ?? false; - } - /// /// Returns a string representation of the style. /// @@ -158,33 +44,17 @@ namespace Avalonia.Styling } } - void IResourceProvider.AddOwner(IResourceHost owner) - { - owner = owner ?? throw new ArgumentNullException(nameof(owner)); - - if (Owner != null) - { - throw new InvalidOperationException("The Style already has a parent."); - } - - Owner = owner; - _resources?.AddOwner(owner); - } - - void IResourceProvider.RemoveOwner(IResourceHost owner) + protected override SelectorMatch Matches(IStyleable target, IStyleHost? host) { - owner = owner ?? throw new ArgumentNullException(nameof(owner)); - - if (Owner == owner) - { - Owner = null; - _resources?.RemoveOwner(owner); - } + return Selector?.Match(target, Parent) ?? + (target == host ? + SelectorMatch.AlwaysThisInstance : + SelectorMatch.NeverThisInstance); } - internal void SetParent(Style? parent) + internal override void SetParent(StyleBase? parent) { - if (parent?.Selector is not null) + if (parent is Style parentStyle && parentStyle.Selector is not null) { if (Selector is null) throw new InvalidOperationException("Child styles must have a selector."); @@ -192,7 +62,7 @@ namespace Avalonia.Styling throw new InvalidOperationException("Child styles must have a nesting selector."); } - Parent = parent; + base.SetParent(parent); } } } diff --git a/src/Avalonia.Base/Styling/StyleBase.cs b/src/Avalonia.Base/Styling/StyleBase.cs new file mode 100644 index 0000000000..0fc57da728 --- /dev/null +++ b/src/Avalonia.Base/Styling/StyleBase.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Metadata; + +namespace Avalonia.Styling +{ + /// + /// Base class for and . + /// + public abstract class StyleBase : AvaloniaObject, IStyle, IResourceProvider + { + private IResourceHost? _owner; + private StyleChildren? _children; + private IResourceDictionary? _resources; + private List? _setters; + private List? _animations; + private StyleCache? _childCache; + + public IList Children => _children ??= new(this); + + public IResourceHost? Owner + { + get => _owner; + private set + { + if (_owner != value) + { + _owner = value; + OwnerChanged?.Invoke(this, EventArgs.Empty); + } + } + } + + public IStyle? Parent { get; private set; } + + public IResourceDictionary Resources + { + get => _resources ?? (Resources = new ResourceDictionary()); + set + { + value = value ?? throw new ArgumentNullException(nameof(value)); + + var hadResources = _resources?.HasResources ?? false; + + _resources = value; + + if (Owner is object) + { + _resources.AddOwner(Owner); + + if (hadResources || _resources.HasResources) + { + Owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); + } + } + } + } + + public IList Setters => _setters ??= new List(); + public IList Animations => _animations ??= new List(); + + bool IResourceNode.HasResources => _resources?.Count > 0; + IReadOnlyList IStyle.Children => (IReadOnlyList?)_children ?? Array.Empty(); + + public void Add(ISetter setter) => Setters.Add(setter); + public void Add(IStyle style) => Children.Add(style); + + public event EventHandler? OwnerChanged; + + public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) + { + target = target ?? throw new ArgumentNullException(nameof(target)); + + var result = SelectorMatchResult.NeverThisType; + + if (_setters?.Count > 0 || _animations?.Count > 0) + { + var match = Matches(target, host); + + if (match.IsMatch) + { + var instance = new StyleInstance(this, target, _setters, _animations, match.Activator); + target.StyleApplied(instance); + instance.Start(); + } + + result = match.Result; + } + + if (_children is not null) + { + _childCache ??= new StyleCache(); + var childResult = _childCache.TryAttach(_children, target, host); + if (childResult > result) + result = childResult; + } + + return result; + } + + public bool TryGetResource(object key, out object? result) + { + result = null; + return _resources?.TryGetResource(key, out result) ?? false; + } + + protected abstract SelectorMatch Matches(IStyleable target, IStyleHost? host); + + internal virtual void SetParent(StyleBase? parent) => Parent = parent; + + void IResourceProvider.AddOwner(IResourceHost owner) + { + owner = owner ?? throw new ArgumentNullException(nameof(owner)); + + if (Owner != null) + { + throw new InvalidOperationException("The Style already has a parent."); + } + + Owner = owner; + _resources?.AddOwner(owner); + } + + void IResourceProvider.RemoveOwner(IResourceHost owner) + { + owner = owner ?? throw new ArgumentNullException(nameof(owner)); + + if (Owner == owner) + { + Owner = null; + _resources?.RemoveOwner(owner); + } + } + } +} diff --git a/src/Avalonia.Base/Styling/StyleChildren.cs b/src/Avalonia.Base/Styling/StyleChildren.cs index 5f8635f155..42b0a331ee 100644 --- a/src/Avalonia.Base/Styling/StyleChildren.cs +++ b/src/Avalonia.Base/Styling/StyleChildren.cs @@ -5,20 +5,20 @@ namespace Avalonia.Styling { internal class StyleChildren : Collection { - private readonly Style _owner; + private readonly StyleBase _owner; - public StyleChildren(Style owner) => _owner = owner; + public StyleChildren(StyleBase owner) => _owner = owner; protected override void InsertItem(int index, IStyle item) { - (item as Style)?.SetParent(_owner); + (item as StyleBase)?.SetParent(_owner); base.InsertItem(index, item); } protected override void RemoveItem(int index) { var item = Items[index]; - (item as Style)?.SetParent(null); + (item as StyleBase)?.SetParent(null); if (_owner.Owner is IResourceHost host) (item as IResourceProvider)?.RemoveOwner(host); base.RemoveItem(index); @@ -26,7 +26,7 @@ namespace Avalonia.Styling protected override void SetItem(int index, IStyle item) { - (item as Style)?.SetParent(_owner); + (item as StyleBase)?.SetParent(_owner); base.SetItem(index, item); if (_owner.Owner is IResourceHost host) (item as IResourceProvider)?.AddOwner(host); From 088d8cfc5c147da723e7641cf77c8dc67646e786 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Jun 2022 13:21:52 +0200 Subject: [PATCH 011/240] Initial implementation of control themes. --- src/Avalonia.Base/Styling/ControlTheme.cs | 27 ++++ src/Avalonia.Base/Styling/IStyle.cs | 2 +- src/Avalonia.Base/Styling/IThemed.cs | 13 ++ src/Avalonia.Base/Styling/NestingSelector.cs | 4 +- src/Avalonia.Base/Styling/Style.cs | 8 +- src/Avalonia.Base/Styling/StyleBase.cs | 20 ++- src/Avalonia.Base/Styling/StyleCache.cs | 2 +- src/Avalonia.Base/Styling/Styler.cs | 14 +++ src/Avalonia.Base/Styling/Styles.cs | 2 +- .../Primitives/TemplatedControl.cs | 25 +++- src/Avalonia.Themes.Default/SimpleTheme.cs | 2 +- src/Avalonia.Themes.Fluent/FluentTheme.cs | 2 +- .../Styling/StyleInclude.cs | 2 +- .../TemplatedControlTests_Theming.cs | 119 ++++++++++++++++++ 14 files changed, 227 insertions(+), 15 deletions(-) create mode 100644 src/Avalonia.Base/Styling/ControlTheme.cs create mode 100644 src/Avalonia.Base/Styling/IThemed.cs create mode 100644 tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests_Theming.cs diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs new file mode 100644 index 0000000000..54fc972c31 --- /dev/null +++ b/src/Avalonia.Base/Styling/ControlTheme.cs @@ -0,0 +1,27 @@ +using System; + +namespace Avalonia.Styling +{ + /// + /// Defines a switchable theme for a control. + /// + public class ControlTheme : StyleBase + { + /// + /// Gets or sets the type for which this control theme is intended. + /// + public Type? TargetType { get; set; } + + internal override bool HasSelector => TargetType is not null; + + internal override SelectorMatch Match(IStyleable control, object? host, bool subscribe) + { + if (TargetType is null) + throw new InvalidOperationException("ControlTheme has no TargetType."); + + return control.StyleKey == TargetType ? + SelectorMatch.AlwaysThisType : + SelectorMatch.NeverThisType; + } + } +} diff --git a/src/Avalonia.Base/Styling/IStyle.cs b/src/Avalonia.Base/Styling/IStyle.cs index e9faf82c07..417739fb28 100644 --- a/src/Avalonia.Base/Styling/IStyle.cs +++ b/src/Avalonia.Base/Styling/IStyle.cs @@ -23,6 +23,6 @@ namespace Avalonia.Styling /// /// A describing how the style matches the control. /// - SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host); + SelectorMatchResult TryAttach(IStyleable target, object? host); } } diff --git a/src/Avalonia.Base/Styling/IThemed.cs b/src/Avalonia.Base/Styling/IThemed.cs new file mode 100644 index 0000000000..32ae515bcb --- /dev/null +++ b/src/Avalonia.Base/Styling/IThemed.cs @@ -0,0 +1,13 @@ +namespace Avalonia.Styling +{ + /// + /// Represents a themed element. + /// + public interface IThemed + { + /// + /// Gets the theme style for the element. + /// + public ControlTheme? Theme { get; } + } +} diff --git a/src/Avalonia.Base/Styling/NestingSelector.cs b/src/Avalonia.Base/Styling/NestingSelector.cs index 481a937867..6d31f7cb18 100644 --- a/src/Avalonia.Base/Styling/NestingSelector.cs +++ b/src/Avalonia.Base/Styling/NestingSelector.cs @@ -15,9 +15,9 @@ namespace Avalonia.Styling protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) { - if (parent is Style s && s.Selector is Selector selector) + if (parent is StyleBase s && s.HasSelector) { - return selector.Match(control, (parent as Style)?.Parent, subscribe); + return s.Match(control, null, subscribe); } throw new InvalidOperationException( diff --git a/src/Avalonia.Base/Styling/Style.cs b/src/Avalonia.Base/Styling/Style.cs index c85c85fe21..ca20ff2b4b 100644 --- a/src/Avalonia.Base/Styling/Style.cs +++ b/src/Avalonia.Base/Styling/Style.cs @@ -28,6 +28,8 @@ namespace Avalonia.Styling /// public Selector? Selector { get; set; } + internal override bool HasSelector => Selector is not null; + /// /// Returns a string representation of the style. /// @@ -44,10 +46,10 @@ namespace Avalonia.Styling } } - protected override SelectorMatch Matches(IStyleable target, IStyleHost? host) + internal override SelectorMatch Match(IStyleable control, object? host, bool subscribe) { - return Selector?.Match(target, Parent) ?? - (target == host ? + return Selector?.Match(control, Parent, subscribe) ?? + (control == host ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance); } diff --git a/src/Avalonia.Base/Styling/StyleBase.cs b/src/Avalonia.Base/Styling/StyleBase.cs index 0fc57da728..b6bfec62bd 100644 --- a/src/Avalonia.Base/Styling/StyleBase.cs +++ b/src/Avalonia.Base/Styling/StyleBase.cs @@ -64,12 +64,14 @@ namespace Avalonia.Styling bool IResourceNode.HasResources => _resources?.Count > 0; IReadOnlyList IStyle.Children => (IReadOnlyList?)_children ?? Array.Empty(); + internal abstract bool HasSelector { get; } + public void Add(ISetter setter) => Setters.Add(setter); public void Add(IStyle style) => Children.Add(style); public event EventHandler? OwnerChanged; - public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) + public SelectorMatchResult TryAttach(IStyleable target, object? host) { target = target ?? throw new ArgumentNullException(nameof(target)); @@ -77,7 +79,7 @@ namespace Avalonia.Styling if (_setters?.Count > 0 || _animations?.Count > 0) { - var match = Matches(target, host); + var match = Match(target, host, subscribe: true); if (match.IsMatch) { @@ -106,7 +108,19 @@ namespace Avalonia.Styling return _resources?.TryGetResource(key, out result) ?? false; } - protected abstract SelectorMatch Matches(IStyleable target, IStyleHost? host); + /// + /// Evaluates the style's selector against the specified target element. + /// + /// The control. + /// The element that hosts the style. + /// + /// Whether the match should subscribe to changes in order to track the match over time, + /// or simply return an immediate result. + /// + /// + /// A describing how the style matches the control. + /// + internal abstract SelectorMatch Match(IStyleable control, object? host, bool subscribe); internal virtual void SetParent(StyleBase? parent) => Parent = parent; diff --git a/src/Avalonia.Base/Styling/StyleCache.cs b/src/Avalonia.Base/Styling/StyleCache.cs index 3285476880..81196f6a27 100644 --- a/src/Avalonia.Base/Styling/StyleCache.cs +++ b/src/Avalonia.Base/Styling/StyleCache.cs @@ -12,7 +12,7 @@ namespace Avalonia.Styling /// internal class StyleCache : Dictionary?> { - public SelectorMatchResult TryAttach(IList styles, IStyleable target, IStyleHost? host) + public SelectorMatchResult TryAttach(IList styles, IStyleable target, object? host) { if (TryGetValue(target.StyleKey, out var cached)) { diff --git a/src/Avalonia.Base/Styling/Styler.cs b/src/Avalonia.Base/Styling/Styler.cs index 74cf77ea40..b9359b3329 100644 --- a/src/Avalonia.Base/Styling/Styler.cs +++ b/src/Avalonia.Base/Styling/Styler.cs @@ -10,6 +10,20 @@ namespace Avalonia.Styling { target = target ?? throw new ArgumentNullException(nameof(target)); + // If the control has a themed templated parent then first apply the styles from + // the templated parent theme. + if (target.TemplatedParent is IThemed themedTemplatedParent) + { + themedTemplatedParent.Theme?.TryAttach(target, themedTemplatedParent); + } + + // If the control itself is themed, then next apply the control theme. + if (target is IThemed themed) + { + themed.Theme?.TryAttach(target, target); + } + + // Apply styles from the rest of the tree. if (target is IStyleHost styleHost) { ApplyStyles(target, styleHost); diff --git a/src/Avalonia.Base/Styling/Styles.cs b/src/Avalonia.Base/Styling/Styles.cs index 7c0bc4ad7f..4c011f1b0d 100644 --- a/src/Avalonia.Base/Styling/Styles.cs +++ b/src/Avalonia.Base/Styling/Styles.cs @@ -109,7 +109,7 @@ namespace Avalonia.Styling set => _styles[index] = value; } - public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) + public SelectorMatchResult TryAttach(IStyleable target, object? host) { _cache ??= new StyleCache(); return _cache.TryAttach(this, target, host); diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index db029d38c0..e1f42b6eb0 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls.Primitives /// /// A lookless control whose visual appearance is defined by its . /// - public class TemplatedControl : Control, ITemplatedControl + public class TemplatedControl : Control, IThemed, ITemplatedControl { /// /// Defines the property. @@ -86,6 +86,12 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty TemplateProperty = AvaloniaProperty.Register(nameof(Template)); + /// + /// Defines the property. + /// + public static readonly StyledProperty ThemeProperty = + AvaloniaProperty.Register(nameof(Theme)); + /// /// Defines the IsTemplateFocusTarget attached property. /// @@ -228,6 +234,15 @@ namespace Avalonia.Controls.Primitives set { SetValue(TemplateProperty, value); } } + /// + /// Gets or sets the theme to be applied to the control. + /// + public ControlTheme? Theme + { + get { return GetValue(ThemeProperty); } + set { SetValue(ThemeProperty, value); } + } + /// /// Gets the value of the IsTemplateFocusTargetProperty attached property on a control. /// @@ -365,6 +380,14 @@ namespace Avalonia.Controls.Primitives { } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ThemeProperty) + InvalidateStyles(); + } + /// /// Called when the control's template is applied. /// diff --git a/src/Avalonia.Themes.Default/SimpleTheme.cs b/src/Avalonia.Themes.Default/SimpleTheme.cs index 6929660757..d7939a68c1 100644 --- a/src/Avalonia.Themes.Default/SimpleTheme.cs +++ b/src/Avalonia.Themes.Default/SimpleTheme.cs @@ -103,7 +103,7 @@ namespace Avalonia.Themes.Default void IResourceProvider.RemoveOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.RemoveOwner(owner); - public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) => Loaded.TryAttach(target, host); + public SelectorMatchResult TryAttach(IStyleable target, object? host) => Loaded.TryAttach(target, host); public bool TryGetResource(object key, out object? value) { diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs index f6b47a5466..befe669029 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.cs @@ -164,7 +164,7 @@ namespace Avalonia.Themes.Fluent } } - public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) => Loaded.TryAttach(target, host); + public SelectorMatchResult TryAttach(IStyleable target, object? host) => Loaded.TryAttach(target, host); public bool TryGetResource(object key, out object? value) { diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index fa4a27fc50..109e85f1a4 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -82,7 +82,7 @@ namespace Avalonia.Markup.Xaml.Styling } } - public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) => Loaded.TryAttach(target, host); + public SelectorMatchResult TryAttach(IStyleable target, object? host) => Loaded.TryAttach(target, host); public bool TryGetResource(object key, out object? value) { diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests_Theming.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests_Theming.cs new file mode 100644 index 0000000000..b24adfe7ab --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests_Theming.cs @@ -0,0 +1,119 @@ +using System.Linq; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Media; +using Avalonia.Styling; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using Xunit; + +#nullable enable + +namespace Avalonia.Controls.UnitTests.Primitives +{ + public class TemplatedControlTests_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); + + var root = CreateRoot(target); + + Assert.NotNull(target.Template); + var border = Assert.IsType(target.VisualChild); + + Assert.Equal(border.Background, Brushes.Red); + + target.Classes.Add("foo"); + Assert.Equal(border.Background, Brushes.Green); + } + + [Fact] + public void Theme_Is_Detached_When_Theme_Property_Cleared() + { + using var app = UnitTestApplication.Start(TestServices.RealStyler); + var target = CreateTarget(); + var root = CreateRoot(target); + + Assert.NotNull(target.Template); + + target.Theme = null; + Assert.Null(target.Template); + } + + [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); + + Assert.Null(target.Template); + + target.Theme = CreateTheme(); + Assert.Null(target.Template); + + root.LayoutManager.ExecuteLayoutPass(); + + var border = Assert.IsType(target.VisualChild); + Assert.NotNull(target.Template); + Assert.Equal(border.Background, Brushes.Red); + } + + private static ThemedControl CreateTarget() + { + return new ThemedControl + { + Theme = CreateTheme(), + }; + } + + private static ControlTheme CreateTheme() + { + var template = new FuncControlTemplate((o, n) => + new Border { Name = "PART_Border" }); + + return new ControlTheme + { + TargetType = typeof(ThemedControl), + Setters = + { + new Setter(ThemedControl.TemplateProperty, template), + }, + Children = + { + new Style(x => x.Nesting().Template().OfType()) + { + Setters = + { + new Setter(Border.BackgroundProperty, Brushes.Red), + } + }, + new Style(x => x.Nesting().Class("foo").Template().OfType()) + { + Setters = + { + new Setter(Border.BackgroundProperty, Brushes.Green), + } + }, + } + }; + } + + private static TestRoot CreateRoot(IControl child) + { + var result = new TestRoot(child); + result.LayoutManager.ExecuteInitialLayoutPass(); + return result; + } + + private class ThemedControl : TemplatedControl + { + public IVisual? VisualChild => VisualChildren?.SingleOrDefault(); + } + } +} From dee353bb9640278ab2364b1d9b4624d5cbe7a215 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Jun 2022 15:24:15 +0200 Subject: [PATCH 012/240] Support ControlTheme in XAML compiler. --- .../AvaloniaXamlIlCompiler.cs | 1 + .../AvaloniaXamlIlControlThemeTransformer.cs | 39 ++++++++++ .../AvaloniaXamlIlSetterTransformer.cs | 75 +++++++++++++------ .../Xaml/ControlThemeTests.cs | 53 +++++++++++++ .../Xaml/TestTemplatedControl.cs | 8 ++ 5 files changed, 155 insertions(+), 21 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestTemplatedControl.cs diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 1ca7be67a7..20e035f8ff 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -48,6 +48,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions InsertBefore( new AvaloniaXamlIlBindingPathParser(), + new AvaloniaXamlIlControlThemeTransformer(), new AvaloniaXamlIlSelectorTransformer(), new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), new AvaloniaXamlIlPropertyPathTransformer(), diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs new file mode 100644 index 0000000000..1338dc7248 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs @@ -0,0 +1,39 @@ +using System.Linq; +using XamlX; +using XamlX.Ast; +using XamlX.Transform; +using XamlX.Transform.Transformers; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers +{ + class AvaloniaXamlIlControlThemeTransformer : IXamlAstTransformer + { + public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) + { + if (!(node is XamlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.ControlTheme")) + return node; + + // Check if we've already transformed this node. + if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlTargetTypeMetadataNode) + return node; + + var targetTypeNode = on.Children.OfType() + .FirstOrDefault(p => p.Property.GetClrProperty().Name == "TargetType") ?? + throw new XamlParseException("ControlTheme must have a TargetType.", node); + + IXamlType targetType; + + if (targetTypeNode.Values[0] is XamlTypeExtensionNode extension) + targetType = extension.Value.GetClrType(); + else if (targetTypeNode.Values[0] is XamlAstTextNode text) + targetType = TypeReferenceResolver.ResolveType(context, text.Text, false, text, true).GetClrType(); + else + throw new XamlParseException("Could not determine TargetType for ControlTheme.", targetTypeNode); + + return new AvaloniaXamlIlTargetTypeMetadataNode(on, + new XamlAstClrTypeReference(targetTypeNode, targetType, false), + AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs index e816265422..06e34a85a2 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -1,19 +1,14 @@ -using System; using System.Collections.Generic; using System.Linq; -using Avalonia.Data.Core; -using XamlX; using XamlX.Ast; using XamlX.Emit; using XamlX.IL; using XamlX.Transform; -using XamlX.Transform.Transformers; using XamlX.TypeSystem; namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { using XamlParseException = XamlX.XamlParseException; - using XamlLoadException = XamlX.XamlLoadException; class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer { public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) @@ -22,21 +17,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers && on.Type.GetClrType().FullName == "Avalonia.Styling.Setter")) return node; - var parent = context.ParentNodes().OfType() - .FirstOrDefault(p => p.Type.GetClrType().FullName == "Avalonia.Styling.Style"); - - if (parent == null) - throw new XamlParseException( - "Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node); - var selectorProperty = parent.Children.OfType() - .FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector"); - if (selectorProperty == null) - throw new XamlParseException( - "Can not find parent Style Selector", node); - var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode; - if (selector?.TargetType == null) - throw new XamlParseException( - "Can not resolve parent Style Selector type", node); + var targetTypeNode = context.ParentNodes() + .OfType() + .FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style) ?? + throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType", node); IXamlType propType = null; var property = @on.Children.OfType() @@ -50,7 +34,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName, - new XamlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]); + new XamlAstClrTypeReference(targetTypeNode, targetTypeNode.TargetType.GetClrType(), false), property.Values[0]); property.Values = new List {avaloniaPropertyNode}; propType = avaloniaPropertyNode.AvaloniaPropertyType; } @@ -84,6 +68,55 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers return node; } + private (IXamlLineInfo, IXamlType) GetTargetType(AstTransformationContext context, IXamlAstNode node) + { + foreach (var n in context.ParentNodes()) + { + if (n is XamlAstObjectNode parent) + { + switch (parent.Type.GetClrType().FullName) + { + case "Avalonia.Styling.Style": + var selectorProperty = parent.Children.OfType() + .FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector"); + if (selectorProperty == null) + throw new XamlParseException("Can not find parent Style Selector.", node); + var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode; + if (selector?.TargetType != null) + return (selector, selector.TargetType); + throw new XamlParseException( + "Can not resolve parent Style Selector type", node); + + case "Avalonia.Styling.ControlTheme": + var targetTypeProperty = parent.Children.OfType() + .FirstOrDefault(p => p.Property.GetClrProperty().Name == "TargetType"); + if (targetTypeProperty == null) + throw new XamlParseException("ControlTemplate has no TargetType.", parent); + break; + } + } + } + + throw new XamlParseException("'Setter' is only valid inside a 'Style' or 'ControlTheme'.", node); + //var parent = context.ParentNodes().OfType() + // .FirstOrDefault(p => p.Type.GetClrType().FullName == "Avalonia.Styling.Style" || + // p.Type.GetClrType().FullName == "Avalonia.Styling.ControlTheme"); + + //if (parent == null) + // throw new XamlParseException( + // "Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node); + //var selectorProperty = parent.Children.OfType() + // .FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector" || + // p.Property.GetClrProperty().Name == "TargetType"); + //if (selectorProperty == null) + // throw new XamlParseException( + // "Can not find parent Style Selector or ControlTemplate TargetType", node); + //var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode; + //if (selector?.TargetType == null) + // throw new XamlParseException( + // "Can not resolve parent Style Selector type", node); + } + class SetterValueProperty : XamlAstClrProperty { public SetterValueProperty(IXamlLineInfo line, IXamlType setterType, IXamlType targetType, diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs new file mode 100644 index 0000000000..05083537cd --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs @@ -0,0 +1,53 @@ +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml +{ + public class ControlThemeTests : XamlTestBase + { + [Fact] + public void ControlTheme_Can_Be_StaticResource() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = $@" + + + {ControlThemeXaml} + + + +"; + + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var button = Assert.IsType(window.Content); + + window.LayoutManager.ExecuteInitialLayoutPass(); + + Assert.NotNull(button.Template); + + var child = Assert.Single(button.GetVisualChildren()); + var border = Assert.IsType(child); + + Assert.Equal(Brushes.Red, border.Background); + } + } + + private const string ControlThemeXaml = @" + + + + + + + +"; + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestTemplatedControl.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestTemplatedControl.cs new file mode 100644 index 0000000000..0c862bb66a --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestTemplatedControl.cs @@ -0,0 +1,8 @@ +using Avalonia.Controls.Primitives; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml +{ + public class TestTemplatedControl : TemplatedControl + { + } +} From a6dc6b1c887c8a5139be7bf1abba1315c26af0d7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Jun 2022 21:45:45 +0200 Subject: [PATCH 013/240] Prevent ControlTheme as a nested style. --- src/Avalonia.Base/Styling/ControlTheme.cs | 16 +++++++++++ .../Styling/ControlThemeTests.cs | 28 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 tests/Avalonia.Base.UnitTests/Styling/ControlThemeTests.cs diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs index 54fc972c31..9dcbd7d2c4 100644 --- a/src/Avalonia.Base/Styling/ControlTheme.cs +++ b/src/Avalonia.Base/Styling/ControlTheme.cs @@ -7,6 +7,17 @@ namespace Avalonia.Styling /// public class ControlTheme : StyleBase { + /// + /// Initializes a new instance of the class. + /// + public ControlTheme() { } + + /// + /// Initializes a new instance of the class. + /// + /// The value for . + public ControlTheme(Type targetType) => TargetType = targetType; + /// /// Gets or sets the type for which this control theme is intended. /// @@ -23,5 +34,10 @@ namespace Avalonia.Styling SelectorMatch.AlwaysThisType : SelectorMatch.NeverThisType; } + + internal override void SetParent(StyleBase? parent) + { + throw new InvalidOperationException("ControlThemes cannot be added as a nested style."); + } } } diff --git a/tests/Avalonia.Base.UnitTests/Styling/ControlThemeTests.cs b/tests/Avalonia.Base.UnitTests/Styling/ControlThemeTests.cs new file mode 100644 index 0000000000..93a0e6c2fd --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Styling/ControlThemeTests.cs @@ -0,0 +1,28 @@ +using System; +using Avalonia.Controls; +using Avalonia.Styling; +using Xunit; + +namespace Avalonia.Base.UnitTests.Styling +{ + public class ControlThemeTests + { + [Fact] + public void ControlTheme_Cannot_Be_Added_To_Style_Children() + { + var target = new ControlTheme(typeof(Button)); + var style = new Style(); + + Assert.Throws(() => style.Children.Add(target)); + } + + [Fact] + public void ControlTheme_Cannot_Be_Added_To_ControlTheme_Children() + { + var target = new ControlTheme(typeof(Button)); + var other = new ControlTheme(typeof(CheckBox)); + + Assert.Throws(() => other.Children.Add(target)); + } + } +} From fc3c036b02afce41d8faca7e4c1e8219fb0c4ceb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Jun 2022 22:48:34 +0200 Subject: [PATCH 014/240] Move Theme to StyledElement. The WPF equivalent (`Style`) is in `FrameworkElement` so it would make sense. Will also make stuff a lot easier and removes the need for an `IThemed` interface. --- src/Avalonia.Base/StyledElement.cs | 25 +++++++++++++++++-- src/Avalonia.Base/Styling/IStyleable.cs | 7 ++++-- src/Avalonia.Base/Styling/IThemed.cs | 13 ---------- src/Avalonia.Base/Styling/Styler.cs | 23 ++++------------- .../Primitives/TemplatedControl.cs | 25 +------------------ .../AvaloniaPropertyConverterTest.cs | 5 ++++ 6 files changed, 39 insertions(+), 59 deletions(-) delete mode 100644 src/Avalonia.Base/Styling/IThemed.cs diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index f98d2cdbcc..4ead2470d7 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -12,8 +12,6 @@ using Avalonia.Logging; using Avalonia.LogicalTree; using Avalonia.Styling; -#nullable enable - namespace Avalonia { /// @@ -55,6 +53,12 @@ namespace Avalonia nameof(TemplatedParent), o => o.TemplatedParent, (o ,v) => o.TemplatedParent = v); + + /// + /// Defines the property. + /// + public static readonly StyledProperty ThemeProperty = + AvaloniaProperty.Register(nameof(Theme)); private int _initCount; private string? _name; @@ -230,6 +234,15 @@ namespace Avalonia internal set => SetAndRaise(TemplatedParentProperty, ref _templatedParent, value); } + /// + /// Gets or sets the theme to be applied to the element. + /// + public ControlTheme? Theme + { + get { return GetValue(ThemeProperty); } + set { SetValue(ThemeProperty, value); } + } + /// /// Gets the styled element's logical children. /// @@ -590,6 +603,14 @@ namespace Avalonia { } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ThemeProperty) + InvalidateStyles(); + } + private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted) { if (o is StyledElement element) diff --git a/src/Avalonia.Base/Styling/IStyleable.cs b/src/Avalonia.Base/Styling/IStyleable.cs index 5bc972e7ab..61fcbdf850 100644 --- a/src/Avalonia.Base/Styling/IStyleable.cs +++ b/src/Avalonia.Base/Styling/IStyleable.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using Avalonia.Collections; using Avalonia.Metadata; -#nullable enable - namespace Avalonia.Styling { /// @@ -28,6 +26,11 @@ namespace Avalonia.Styling /// ITemplatedControl? TemplatedParent { get; } + /// + /// Gets the theme to be applied to the control. + /// + public ControlTheme? Theme { get; } + /// /// Notifies the element that a style has been applied. /// diff --git a/src/Avalonia.Base/Styling/IThemed.cs b/src/Avalonia.Base/Styling/IThemed.cs deleted file mode 100644 index 32ae515bcb..0000000000 --- a/src/Avalonia.Base/Styling/IThemed.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Avalonia.Styling -{ - /// - /// Represents a themed element. - /// - public interface IThemed - { - /// - /// Gets the theme style for the element. - /// - public ControlTheme? Theme { get; } - } -} diff --git a/src/Avalonia.Base/Styling/Styler.cs b/src/Avalonia.Base/Styling/Styler.cs index b9359b3329..c9ea123bdc 100644 --- a/src/Avalonia.Base/Styling/Styler.cs +++ b/src/Avalonia.Base/Styling/Styler.cs @@ -1,33 +1,24 @@ using System; -#nullable enable - namespace Avalonia.Styling { public class Styler : IStyler { public void ApplyStyles(IStyleable target) { - target = target ?? throw new ArgumentNullException(nameof(target)); + _ = target ?? throw new ArgumentNullException(nameof(target)); // If the control has a themed templated parent then first apply the styles from // the templated parent theme. - if (target.TemplatedParent is IThemed themedTemplatedParent) - { - themedTemplatedParent.Theme?.TryAttach(target, themedTemplatedParent); - } + if (target.TemplatedParent is IStyleable styleableParent) + styleableParent.Theme?.TryAttach(target, styleableParent); - // If the control itself is themed, then next apply the control theme. - if (target is IThemed themed) - { - themed.Theme?.TryAttach(target, target); - } + // Next apply the control theme. + target.Theme?.TryAttach(target, target); // Apply styles from the rest of the tree. if (target is IStyleHost styleHost) - { ApplyStyles(target, styleHost); - } } private void ApplyStyles(IStyleable target, IStyleHost host) @@ -35,14 +26,10 @@ namespace Avalonia.Styling var parent = host.StylingParent; if (parent != null) - { ApplyStyles(target, parent); - } if (host.IsStylesInitialized) - { host.Styles.TryAttach(target, host); - } } } } diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index e1f42b6eb0..db029d38c0 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls.Primitives /// /// A lookless control whose visual appearance is defined by its . /// - public class TemplatedControl : Control, IThemed, ITemplatedControl + public class TemplatedControl : Control, ITemplatedControl { /// /// Defines the property. @@ -86,12 +86,6 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty TemplateProperty = AvaloniaProperty.Register(nameof(Template)); - /// - /// Defines the property. - /// - public static readonly StyledProperty ThemeProperty = - AvaloniaProperty.Register(nameof(Theme)); - /// /// Defines the IsTemplateFocusTarget attached property. /// @@ -234,15 +228,6 @@ namespace Avalonia.Controls.Primitives set { SetValue(TemplateProperty, value); } } - /// - /// Gets or sets the theme to be applied to the control. - /// - public ControlTheme? Theme - { - get { return GetValue(ThemeProperty); } - set { SetValue(ThemeProperty, value); } - } - /// /// Gets the value of the IsTemplateFocusTargetProperty attached property on a control. /// @@ -380,14 +365,6 @@ namespace Avalonia.Controls.Primitives { } - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) - { - base.OnPropertyChanged(change); - - if (change.Property == ThemeProperty) - InvalidateStyles(); - } - /// /// Called when the control's template is applied. /// diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs index 33bf72014c..ca59fe8480 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs @@ -137,6 +137,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters get { throw new NotImplementedException(); } } + public ControlTheme Theme + { + get { throw new NotImplementedException(); } + } + public void DetachStyles() { throw new NotImplementedException(); From 8c61f25188afe50b0785150e2e1fbf605c4652c5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Jun 2022 09:23:17 +0200 Subject: [PATCH 015/240] Promote theme to LocalValue if applied from style. --- src/Avalonia.Base/StyledElement.cs | 25 ++- .../TemplatedControlTests_Theming.cs | 146 ++++++++++++------ .../Xaml/ControlThemeTests.cs | 36 +++++ 3 files changed, 157 insertions(+), 50 deletions(-) diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 4ead2470d7..75c4b94174 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -71,6 +71,7 @@ namespace Avalonia private List? _appliedStyles; private ITemplatedControl? _templatedParent; private bool _dataContextUpdating; + private bool _hasPromotedTheme; /// /// Initializes static members of the class. @@ -239,8 +240,8 @@ namespace Avalonia /// public ControlTheme? Theme { - get { return GetValue(ThemeProperty); } - set { SetValue(ThemeProperty, value); } + get => GetValue(ThemeProperty); + set => SetValue(ThemeProperty, value); } /// @@ -315,6 +316,7 @@ namespace Avalonia /// IStyleHost? IStyleHost.StylingParent => (IStyleHost?)InheritanceParent; + /// public virtual void BeginInit() { @@ -354,10 +356,15 @@ namespace Avalonia } finally { + _styled = true; EndBatchUpdate(); } - _styled = true; + if (_hasPromotedTheme) + { + _hasPromotedTheme = false; + ClearValue(ThemeProperty); + } } return _styled; @@ -608,7 +615,19 @@ namespace Avalonia base.OnPropertyChanged(change); if (change.Property == ThemeProperty) + { + // 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 = change.GetNewValue(); + _hasPromotedTheme = true; + } + InvalidateStyles(); + } } private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests_Theming.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests_Theming.cs index b24adfe7ab..74d75ff056 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests_Theming.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests_Theming.cs @@ -13,63 +13,122 @@ namespace Avalonia.Controls.UnitTests.Primitives { public class TemplatedControlTests_Theming { - [Fact] - public void Theme_Is_Applied_When_Attached_To_Logical_Tree() + public class InlineTheme { - using var app = UnitTestApplication.Start(TestServices.RealStyler); - var target = CreateTarget(); + [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); + Assert.Null(target.Template); - var root = CreateRoot(target); + var root = CreateRoot(target); + Assert.NotNull(target.Template); - Assert.NotNull(target.Template); - var border = Assert.IsType(target.VisualChild); - - Assert.Equal(border.Background, Brushes.Red); + var border = Assert.IsType(target.VisualChild); + Assert.Equal(border.Background, Brushes.Red); - target.Classes.Add("foo"); - Assert.Equal(border.Background, Brushes.Green); - } + target.Classes.Add("foo"); + Assert.Equal(border.Background, Brushes.Green); + } - [Fact] - public void Theme_Is_Detached_When_Theme_Property_Cleared() - { - using var app = UnitTestApplication.Start(TestServices.RealStyler); - var target = CreateTarget(); - var root = CreateRoot(target); + [Fact] + public void Theme_Is_Detached_When_Theme_Property_Cleared() + { + using var app = UnitTestApplication.Start(TestServices.RealStyler); + var target = CreateTarget(); + var root = CreateRoot(target); - Assert.NotNull(target.Template); + Assert.NotNull(target.Template); - target.Theme = null; - Assert.Null(target.Template); - } + target.Theme = null; + Assert.Null(target.Template); + } - [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); + [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); + + Assert.Null(target.Template); - Assert.Null(target.Template); + target.Theme = CreateTheme(); + Assert.Null(target.Template); - target.Theme = CreateTheme(); - Assert.Null(target.Template); + root.LayoutManager.ExecuteLayoutPass(); - root.LayoutManager.ExecuteLayoutPass(); + var border = Assert.IsType(target.VisualChild); + Assert.NotNull(target.Template); + Assert.Equal(border.Background, Brushes.Red); + } - var border = Assert.IsType(target.VisualChild); - Assert.NotNull(target.Template); - Assert.Equal(border.Background, Brushes.Red); + private static ThemedControl CreateTarget() + { + return new ThemedControl + { + Theme = CreateTheme(), + }; + } + + private static TestRoot CreateRoot(IControl child) + { + var result = new TestRoot(child); + result.LayoutManager.ExecuteInitialLayoutPass(); + return result; + } } - private static ThemedControl CreateTarget() + public class ThemeFromStyle { - return new ThemedControl + [Fact] + public void Theme_Is_Applied_When_Attached_To_Logical_Tree() { - Theme = CreateTheme(), - }; + using var app = UnitTestApplication.Start(TestServices.RealStyler); + var target = CreateTarget(); + + Assert.Null(target.Theme); + Assert.Null(target.Template); + + var root = CreateRoot(target); + + Assert.NotNull(target.Theme); + Assert.NotNull(target.Template); + + var border = Assert.IsType(target.VisualChild); + Assert.Equal(border.Background, Brushes.Red); + + target.Classes.Add("foo"); + Assert.Equal(border.Background, Brushes.Green); + } + + private static ThemedControl CreateTarget() + { + return new ThemedControl(); + } + + private static TestRoot CreateRoot(IControl child) + { + var result = new TestRoot() + { + Styles = + { + new Style(x => x.OfType()) + { + Setters = + { + new Setter(TemplatedControl.ThemeProperty, CreateTheme()) + } + } + } + }; + + result.Child = child; + result.LayoutManager.ExecuteInitialLayoutPass(); + return result; + } } private static ControlTheme CreateTheme() @@ -104,13 +163,6 @@ namespace Avalonia.Controls.UnitTests.Primitives }; } - private static TestRoot CreateRoot(IControl child) - { - var result = new TestRoot(child); - result.LayoutManager.ExecuteInitialLayoutPass(); - return result; - } - private class ThemedControl : TemplatedControl { public IVisual? VisualChild => VisualChildren?.SingleOrDefault(); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs index 05083537cd..9eb48311df 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs @@ -38,6 +38,42 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } } + [Fact] + public void ControlTheme_Can_Be_Set_In_Style() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = $@" + + + {ControlThemeXaml} + + + + + + + +"; + + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var button = Assert.IsType(window.Content); + + window.LayoutManager.ExecuteInitialLayoutPass(); + + Assert.NotNull(button.Template); + + var child = Assert.Single(button.GetVisualChildren()); + var border = Assert.IsType(child); + + Assert.Equal(Brushes.Red, border.Background); + } + } + private const string ControlThemeXaml = @" From 5cd95320128fa97fff7af8223cf55b9d043086f8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Jun 2022 09:36:53 +0200 Subject: [PATCH 016/240] Move tests to correct place. --- .../Styling/StyledElementTests_Theming.cs | 169 +++++++++++++++++ .../TemplatedControlTests_Theming.cs | 171 ------------------ 2 files changed, 169 insertions(+), 171 deletions(-) create mode 100644 tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs delete mode 100644 tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests_Theming.cs diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs new file mode 100644 index 0000000000..539f9e6576 --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs @@ -0,0 +1,169 @@ +using System.Linq; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Media; +using Avalonia.Styling; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using Xunit; + +namespace Avalonia.Base.UnitTests.Styling; + +public class StyledElementTests_Theming +{ + public class InlineTheme + { + [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); + + var root = CreateRoot(target); + Assert.NotNull(target.Template); + + var border = Assert.IsType(target.VisualChild); + Assert.Equal(border.Background, Brushes.Red); + + target.Classes.Add("foo"); + Assert.Equal(border.Background, Brushes.Green); + } + + [Fact] + public void Theme_Is_Detached_When_Theme_Property_Cleared() + { + using var app = UnitTestApplication.Start(TestServices.RealStyler); + var target = CreateTarget(); + var root = CreateRoot(target); + + Assert.NotNull(target.Template); + + target.Theme = null; + Assert.Null(target.Template); + } + + [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); + + Assert.Null(target.Template); + + target.Theme = CreateTheme(); + Assert.Null(target.Template); + + root.LayoutManager.ExecuteLayoutPass(); + + var border = Assert.IsType(target.VisualChild); + Assert.NotNull(target.Template); + Assert.Equal(border.Background, Brushes.Red); + } + + private static ThemedControl CreateTarget() + { + return new ThemedControl + { + Theme = CreateTheme(), + }; + } + + private static TestRoot CreateRoot(IControl child) + { + var result = new TestRoot(child); + result.LayoutManager.ExecuteInitialLayoutPass(); + return result; + } + } + + public class ThemeFromStyle + { + [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); + Assert.Null(target.Template); + + var root = CreateRoot(target); + + Assert.NotNull(target.Theme); + Assert.NotNull(target.Template); + + var border = Assert.IsType(target.VisualChild); + Assert.Equal(border.Background, Brushes.Red); + + target.Classes.Add("foo"); + Assert.Equal(border.Background, Brushes.Green); + } + + private static ThemedControl CreateTarget() + { + return new ThemedControl(); + } + + private static TestRoot CreateRoot(IControl child) + { + var result = new TestRoot() + { + Styles = + { + new Style(x => x.OfType()) + { + Setters = + { + new Setter(TemplatedControl.ThemeProperty, CreateTheme()) + } + } + } + }; + + result.Child = child; + result.LayoutManager.ExecuteInitialLayoutPass(); + return result; + } + } + + private static ControlTheme CreateTheme() + { + var template = new FuncControlTemplate((o, n) => + new Border { Name = "PART_Border" }); + + return new ControlTheme + { + TargetType = typeof(ThemedControl), + Setters = + { + new Setter(ThemedControl.TemplateProperty, template), + }, + Children = + { + new Style(x => x.Nesting().Template().OfType()) + { + Setters = + { + new Setter(Border.BackgroundProperty, Brushes.Red), + } + }, + new Style(x => x.Nesting().Class("foo").Template().OfType()) + { + Setters = + { + new Setter(Border.BackgroundProperty, Brushes.Green), + } + }, + } + }; + } + + private class ThemedControl : TemplatedControl + { + public IVisual? VisualChild => VisualChildren?.SingleOrDefault(); + } +} diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests_Theming.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests_Theming.cs deleted file mode 100644 index 74d75ff056..0000000000 --- a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests_Theming.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System.Linq; -using Avalonia.Controls.Primitives; -using Avalonia.Controls.Templates; -using Avalonia.Media; -using Avalonia.Styling; -using Avalonia.UnitTests; -using Avalonia.VisualTree; -using Xunit; - -#nullable enable - -namespace Avalonia.Controls.UnitTests.Primitives -{ - public class TemplatedControlTests_Theming - { - public class InlineTheme - { - [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); - - var root = CreateRoot(target); - Assert.NotNull(target.Template); - - var border = Assert.IsType(target.VisualChild); - Assert.Equal(border.Background, Brushes.Red); - - target.Classes.Add("foo"); - Assert.Equal(border.Background, Brushes.Green); - } - - [Fact] - public void Theme_Is_Detached_When_Theme_Property_Cleared() - { - using var app = UnitTestApplication.Start(TestServices.RealStyler); - var target = CreateTarget(); - var root = CreateRoot(target); - - Assert.NotNull(target.Template); - - target.Theme = null; - Assert.Null(target.Template); - } - - [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); - - Assert.Null(target.Template); - - target.Theme = CreateTheme(); - Assert.Null(target.Template); - - root.LayoutManager.ExecuteLayoutPass(); - - var border = Assert.IsType(target.VisualChild); - Assert.NotNull(target.Template); - Assert.Equal(border.Background, Brushes.Red); - } - - private static ThemedControl CreateTarget() - { - return new ThemedControl - { - Theme = CreateTheme(), - }; - } - - private static TestRoot CreateRoot(IControl child) - { - var result = new TestRoot(child); - result.LayoutManager.ExecuteInitialLayoutPass(); - return result; - } - } - - public class ThemeFromStyle - { - [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); - Assert.Null(target.Template); - - var root = CreateRoot(target); - - Assert.NotNull(target.Theme); - Assert.NotNull(target.Template); - - var border = Assert.IsType(target.VisualChild); - Assert.Equal(border.Background, Brushes.Red); - - target.Classes.Add("foo"); - Assert.Equal(border.Background, Brushes.Green); - } - - private static ThemedControl CreateTarget() - { - return new ThemedControl(); - } - - private static TestRoot CreateRoot(IControl child) - { - var result = new TestRoot() - { - Styles = - { - new Style(x => x.OfType()) - { - Setters = - { - new Setter(TemplatedControl.ThemeProperty, CreateTheme()) - } - } - } - }; - - result.Child = child; - result.LayoutManager.ExecuteInitialLayoutPass(); - return result; - } - } - - private static ControlTheme CreateTheme() - { - var template = new FuncControlTemplate((o, n) => - new Border { Name = "PART_Border" }); - - return new ControlTheme - { - TargetType = typeof(ThemedControl), - Setters = - { - new Setter(ThemedControl.TemplateProperty, template), - }, - Children = - { - new Style(x => x.Nesting().Template().OfType()) - { - Setters = - { - new Setter(Border.BackgroundProperty, Brushes.Red), - } - }, - new Style(x => x.Nesting().Class("foo").Template().OfType()) - { - Setters = - { - new Setter(Border.BackgroundProperty, Brushes.Green), - } - }, - } - }; - } - - private class ThemedControl : TemplatedControl - { - public IVisual? VisualChild => VisualChildren?.SingleOrDefault(); - } - } -} From 4bdcb8eeeaab2f790245ddb70a5cfca3df7886f8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Jun 2022 10:34:34 +0200 Subject: [PATCH 017/240] Invalidate template control styles when Theme changes. --- .../Primitives/TemplatedControl.cs | 11 +++ .../Styling/StyledElementTests_Theming.cs | 75 ++++++++++++++----- 2 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index db029d38c0..a07dd9ae27 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -365,6 +365,17 @@ namespace Avalonia.Controls.Primitives { } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ThemeProperty) + { + foreach (var child in this.GetTemplateChildren()) + child.InvalidateStyles(); + } + } + /// /// Called when the control's template is applied. /// diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs index 539f9e6576..0c0808987a 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs @@ -8,6 +8,8 @@ using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; +#nullable enable + namespace Avalonia.Base.UnitTests.Styling; public class StyledElementTests_Theming @@ -45,6 +47,40 @@ public class StyledElementTests_Theming Assert.Null(target.Template); } + [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), + Children = + { + new Style(x => x.Nesting().Template().OfType()) + { + Setters = + { + new Setter(Canvas.BackgroundProperty, Brushes.Red), + } + }, + } + }; + + var target = CreateTarget(theme); + target.Template = new FuncControlTemplate((o, n) => new Canvas()); + + var root = CreateRoot(target); + + var canvas = Assert.IsType(target.VisualChild); + Assert.Equal(canvas.Background, Brushes.Red); + + target.Theme = null; + + Assert.IsType(target.VisualChild); + Assert.Null(canvas.Background); + } + [Fact] public void Theme_Is_Applied_On_Layout_After_Theme_Property_Changes() { @@ -64,11 +100,11 @@ public class StyledElementTests_Theming Assert.Equal(border.Background, Brushes.Red); } - private static ThemedControl CreateTarget() + private static ThemedControl CreateTarget(ControlTheme? theme = null) { return new ThemedControl { - Theme = CreateTheme(), + Theme = theme ?? CreateTheme(), }; } @@ -132,33 +168,32 @@ public class StyledElementTests_Theming private static ControlTheme CreateTheme() { - var template = new FuncControlTemplate((o, n) => - new Border { Name = "PART_Border" }); + var template = new FuncControlTemplate((o, n) => new Border()); return new ControlTheme { TargetType = typeof(ThemedControl), Setters = - { - new Setter(ThemedControl.TemplateProperty, template), - }, - Children = - { - new Style(x => x.Nesting().Template().OfType()) { - Setters = - { - new Setter(Border.BackgroundProperty, Brushes.Red), - } + new Setter(ThemedControl.TemplateProperty, template), }, - new Style(x => x.Nesting().Class("foo").Template().OfType()) + Children = { - Setters = + new Style(x => x.Nesting().Template().OfType()) { - new Setter(Border.BackgroundProperty, Brushes.Green), - } - }, - } + Setters = + { + new Setter(Border.BackgroundProperty, Brushes.Red), + } + }, + new Style(x => x.Nesting().Class("foo").Template().OfType()) + { + Setters = + { + new Setter(Border.BackgroundProperty, Brushes.Green), + } + }, + } }; } From 49613c7bceb5244444df97862b59285915a8e4ee Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 3 Jun 2022 12:04:37 +0200 Subject: [PATCH 018/240] Add accent button to control catalog. --- samples/ControlCatalog/Pages/ButtonsPage.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/ControlCatalog/Pages/ButtonsPage.xaml b/samples/ControlCatalog/Pages/ButtonsPage.xaml index 059b4d9788..8a474a203d 100644 --- a/samples/ControlCatalog/Pages/ButtonsPage.xaml +++ b/samples/ControlCatalog/Pages/ButtonsPage.xaml @@ -90,6 +90,7 @@ + Date: Fri, 3 Jun 2022 14:32:19 +0200 Subject: [PATCH 019/240] Fix nested :not selector. --- src/Avalonia.Base/Styling/NotSelector.cs | 2 +- .../Styling/SelectorTests_Nesting.cs | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Styling/NotSelector.cs b/src/Avalonia.Base/Styling/NotSelector.cs index cdc3254d38..76a0690e96 100644 --- a/src/Avalonia.Base/Styling/NotSelector.cs +++ b/src/Avalonia.Base/Styling/NotSelector.cs @@ -67,6 +67,6 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => _previous; - internal override bool HasValidNestingSelector() => _argument.HasValidNestingSelector(); + internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false; } } diff --git a/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs b/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs index d49fcf03a2..1520dc329d 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs @@ -257,6 +257,30 @@ namespace Avalonia.Base.UnitTests.Styling parent.Children.Add(child); } + + [Fact] + public void Nesting_Not_Class_Matches() + { + var control = new Control1 { Classes = { "foo" } }; + Style nested; + var parent = new Style(x => x.OfType()) + { + Children = + { + (nested = new Style(x => x.Nesting().Not(y => y.Class("foo")))), + } + }; + + var match = nested.Selector.Match(control, parent); + Assert.Equal(SelectorMatchResult.Sometimes, match.Result); + + var sink = new ActivatorSink(match.Activator); + + Assert.False(sink.Active); + control.Classes.Clear(); + Assert.True(sink.Active); + } + public class Control1 : Control { } From 05fdc0446416a285c9067b56d858c018e4f7104d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 3 Jun 2022 23:43:55 +0200 Subject: [PATCH 020/240] Add ControlTheme.BasedOn. --- src/Avalonia.Base/Styling/ControlTheme.cs | 26 ++++++-- src/Avalonia.Base/Styling/NestingSelector.cs | 12 +++- src/Avalonia.Base/Styling/Style.cs | 35 ++++++++--- src/Avalonia.Base/Styling/StyleBase.cs | 61 ++++++------------- .../Styling/StyledElementTests_Theming.cs | 59 ++++++++++++++++-- 5 files changed, 129 insertions(+), 64 deletions(-) diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs index 9dcbd7d2c4..aff6fad990 100644 --- a/src/Avalonia.Base/Styling/ControlTheme.cs +++ b/src/Avalonia.Base/Styling/ControlTheme.cs @@ -23,16 +23,32 @@ namespace Avalonia.Styling /// public Type? TargetType { get; set; } - internal override bool HasSelector => TargetType is not null; + /// + /// Gets or sets a control theme that is the basis of the current theme. + /// + public ControlTheme? BasedOn { get; set; } - internal override SelectorMatch Match(IStyleable control, object? host, bool subscribe) + public override SelectorMatchResult TryAttach(IStyleable target, object? host) { + _ = target ?? throw new ArgumentNullException(nameof(target)); + if (TargetType is null) throw new InvalidOperationException("ControlTheme has no TargetType."); - return control.StyleKey == TargetType ? - SelectorMatch.AlwaysThisType : - SelectorMatch.NeverThisType; + var result = BasedOn?.TryAttach(target, host) ?? SelectorMatchResult.NeverThisType; + + if (HasSettersOrAnimations && target.StyleKey == TargetType) + { + Attach(target, null); + result = SelectorMatchResult.AlwaysThisType; + } + + var childResult = TryAttachChildren(target, host); + + if (childResult > result) + result = childResult; + + return result; } internal override void SetParent(StyleBase? parent) diff --git a/src/Avalonia.Base/Styling/NestingSelector.cs b/src/Avalonia.Base/Styling/NestingSelector.cs index 6d31f7cb18..c8945a713d 100644 --- a/src/Avalonia.Base/Styling/NestingSelector.cs +++ b/src/Avalonia.Base/Styling/NestingSelector.cs @@ -15,9 +15,17 @@ namespace Avalonia.Styling protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) { - if (parent is StyleBase s && s.HasSelector) + if (parent is Style s && s.Selector is not null) { - return s.Match(control, null, subscribe); + return s.Selector.Match(control, s.Parent, subscribe); + } + else if (parent is ControlTheme theme) + { + if (theme.TargetType is null) + throw new InvalidOperationException("ControlTheme has no TargetType."); + return control.StyleKey == theme.TargetType ? + SelectorMatch.AlwaysThisType : + SelectorMatch.NeverThisType; } throw new InvalidOperationException( diff --git a/src/Avalonia.Base/Styling/Style.cs b/src/Avalonia.Base/Styling/Style.cs index ca20ff2b4b..7a6b746488 100644 --- a/src/Avalonia.Base/Styling/Style.cs +++ b/src/Avalonia.Base/Styling/Style.cs @@ -28,7 +28,32 @@ namespace Avalonia.Styling /// public Selector? Selector { get; set; } - internal override bool HasSelector => Selector is not null; + 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. @@ -46,14 +71,6 @@ namespace Avalonia.Styling } } - internal override SelectorMatch Match(IStyleable control, object? host, bool subscribe) - { - return Selector?.Match(control, Parent, subscribe) ?? - (control == host ? - SelectorMatch.AlwaysThisInstance : - SelectorMatch.NeverThisInstance); - } - internal override void SetParent(StyleBase? parent) { if (parent is Style parentStyle && parentStyle.Selector is not null) diff --git a/src/Avalonia.Base/Styling/StyleBase.cs b/src/Avalonia.Base/Styling/StyleBase.cs index b6bfec62bd..306a4cf010 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.Styling.Activators; namespace Avalonia.Styling { @@ -64,43 +65,14 @@ namespace Avalonia.Styling bool IResourceNode.HasResources => _resources?.Count > 0; IReadOnlyList IStyle.Children => (IReadOnlyList?)_children ?? Array.Empty(); - internal abstract bool HasSelector { get; } + internal bool HasSettersOrAnimations => _setters?.Count > 0 || _animations?.Count > 0; public void Add(ISetter setter) => Setters.Add(setter); public void Add(IStyle style) => Children.Add(style); public event EventHandler? OwnerChanged; - public SelectorMatchResult TryAttach(IStyleable target, object? host) - { - target = target ?? throw new ArgumentNullException(nameof(target)); - - var result = SelectorMatchResult.NeverThisType; - - if (_setters?.Count > 0 || _animations?.Count > 0) - { - var match = Match(target, host, subscribe: true); - - if (match.IsMatch) - { - var instance = new StyleInstance(this, target, _setters, _animations, match.Activator); - target.StyleApplied(instance); - instance.Start(); - } - - result = match.Result; - } - - if (_children is not null) - { - _childCache ??= new StyleCache(); - var childResult = _childCache.TryAttach(_children, target, host); - if (childResult > result) - result = childResult; - } - - return result; - } + public abstract SelectorMatchResult TryAttach(IStyleable target, object? host); public bool TryGetResource(object key, out object? result) { @@ -108,19 +80,20 @@ namespace Avalonia.Styling return _resources?.TryGetResource(key, out result) ?? false; } - /// - /// Evaluates the style's selector against the specified target element. - /// - /// The control. - /// The element that hosts the style. - /// - /// Whether the match should subscribe to changes in order to track the match over time, - /// or simply return an immediate result. - /// - /// - /// A describing how the style matches the control. - /// - internal abstract SelectorMatch Match(IStyleable control, object? host, bool subscribe); + internal void Attach(IStyleable target, IStyleActivator? activator) + { + var instance = new StyleInstance(this, target, _setters, _animations, activator); + target.StyleApplied(instance); + instance.Start(); + } + + 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; diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs index 0c0808987a..737cf1e048 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs @@ -28,10 +28,10 @@ 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] @@ -73,7 +73,7 @@ public class StyledElementTests_Theming var root = CreateRoot(target); var canvas = Assert.IsType(target.VisualChild); - Assert.Equal(canvas.Background, Brushes.Red); + Assert.Equal(Brushes.Red, canvas.Background); target.Theme = null; @@ -97,7 +97,28 @@ public class StyledElementTests_Theming var border = Assert.IsType(target.VisualChild); Assert.NotNull(target.Template); - Assert.Equal(border.Background, Brushes.Red); + Assert.Equal(Brushes.Red, border.Background); + } + + [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); + + var root = CreateRoot(target); + Assert.NotNull(target.Template); + Assert.Equal(Brushes.Blue, target.BorderBrush); + + var border = Assert.IsType(target.VisualChild); + Assert.Equal(Brushes.Red, border.Background); + Assert.Equal(Brushes.Yellow, border.BorderBrush); + + target.Classes.Add("foo"); + Assert.Equal(Brushes.Green, border.Background); + Assert.Equal(Brushes.Cyan, border.BorderBrush); } private static ThemedControl CreateTarget(ControlTheme? theme = null) @@ -197,6 +218,36 @@ public class StyledElementTests_Theming }; } + private static ControlTheme CreateDerivedTheme() + { + return new ControlTheme + { + TargetType = typeof(ThemedControl), + BasedOn = CreateTheme(), + Setters = + { + new Setter(Border.BorderBrushProperty, Brushes.Blue), + }, + Children = + { + new Style(x => x.Nesting().Template().OfType()) + { + Setters = + { + new Setter(Border.BorderBrushProperty, Brushes.Yellow), + } + }, + new Style(x => x.Nesting().Class("foo").Template().OfType()) + { + Setters = + { + new Setter(Border.BorderBrushProperty, Brushes.Cyan), + } + }, + } + }; + } + private class ThemedControl : TemplatedControl { public IVisual? VisualChild => VisualChildren?.SingleOrDefault(); From 1d1ef5ca9fdb42d2fbf029f122530dcca9b51a59 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 4 Jun 2022 12:45:19 +0200 Subject: [PATCH 021/240] Display control themes in devtools. --- src/Avalonia.Base/Styling/ControlTheme.cs | 8 ++++++++ .../Diagnostics/ViewModels/ControlDetailsViewModel.cs | 11 +++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs index aff6fad990..399eb9ae59 100644 --- a/src/Avalonia.Base/Styling/ControlTheme.cs +++ b/src/Avalonia.Base/Styling/ControlTheme.cs @@ -51,6 +51,14 @@ namespace Avalonia.Styling return result; } + public override string ToString() + { + if (TargetType is not null) + return "ControlTheme: " + TargetType.Name; + else + return "ControlTheme"; + } + internal override void SetParent(StyleBase? parent) { throw new InvalidOperationException("ControlThemes cannot be added as a nested style."); diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index e383c160e3..795826e4f6 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -67,8 +67,15 @@ namespace Avalonia.Diagnostics.ViewModels var setters = new List(); - if (styleSource is Style style) + if (styleSource is StyleBase style) { + var selector = style switch + { + Style s => s.Selector?.ToString(), + ControlTheme t => t.TargetType?.Name.ToString(), + _ => null, + }; + foreach (var setter in style.Setters) { if (setter is Setter regularSetter @@ -105,7 +112,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - AppliedStyles.Add(new StyleViewModel(appliedStyle, style.Selector?.ToString() ?? "No selector", setters)); + AppliedStyles.Add(new StyleViewModel(appliedStyle, selector ?? "No selector", setters)); } } From 95f70143ca319e5e6f49601d1e2de60bdbb39759 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 4 Jun 2022 14:53:31 +0200 Subject: [PATCH 022/240] Can apply control theme to derived types. --- src/Avalonia.Base/Styling/ControlTheme.cs | 2 +- src/Avalonia.Base/Styling/NestingSelector.cs | 2 +- .../Styling/StyledElementTests_Theming.cs | 25 +++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs index 399eb9ae59..644e8b32d4 100644 --- a/src/Avalonia.Base/Styling/ControlTheme.cs +++ b/src/Avalonia.Base/Styling/ControlTheme.cs @@ -37,7 +37,7 @@ namespace Avalonia.Styling var result = BasedOn?.TryAttach(target, host) ?? SelectorMatchResult.NeverThisType; - if (HasSettersOrAnimations && target.StyleKey == TargetType) + if (HasSettersOrAnimations && TargetType.IsAssignableFrom(target.StyleKey)) { Attach(target, null); result = SelectorMatchResult.AlwaysThisType; diff --git a/src/Avalonia.Base/Styling/NestingSelector.cs b/src/Avalonia.Base/Styling/NestingSelector.cs index c8945a713d..4393d3239f 100644 --- a/src/Avalonia.Base/Styling/NestingSelector.cs +++ b/src/Avalonia.Base/Styling/NestingSelector.cs @@ -23,7 +23,7 @@ namespace Avalonia.Styling { if (theme.TargetType is null) throw new InvalidOperationException("ControlTheme has no TargetType."); - return control.StyleKey == theme.TargetType ? + return theme.TargetType.IsAssignableFrom(control.StyleKey) ? SelectorMatch.AlwaysThisType : SelectorMatch.NeverThisType; } diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs index 737cf1e048..ab6c239393 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs @@ -34,6 +34,27 @@ public class StyledElementTests_Theming Assert.Equal(Brushes.Green, border.Background); } + [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(), + }; + + Assert.Null(target.Template); + + var root = CreateRoot(target); + Assert.NotNull(target.Template); + + var border = Assert.IsType(target.VisualChild); + Assert.Equal(Brushes.Red, border.Background); + + target.Classes.Add("foo"); + Assert.Equal(Brushes.Green, border.Background); + } + [Fact] public void Theme_Is_Detached_When_Theme_Property_Cleared() { @@ -252,4 +273,8 @@ public class StyledElementTests_Theming { public IVisual? VisualChild => VisualChildren?.SingleOrDefault(); } + + private class DerivedThemedControl : ThemedControl + { + } } From d21e634ab308c1b63d1e2f2105de29c8af236b2a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 8 Jun 2022 12:00:14 +0200 Subject: [PATCH 023/240] Added support for implicit themes. If no `Theme` property is provided, try to look up a resource keyed with the control's `StyleKey`. --- src/Avalonia.Base/StyledElement.cs | 28 ++++++++++++ src/Avalonia.Base/Styling/IStyleable.cs | 4 +- src/Avalonia.Base/Styling/Styler.cs | 4 +- .../Styling/StyledElementTests_Theming.cs | 43 +++++++++++++++++++ .../AvaloniaPropertyConverterTest.cs | 4 +- .../DynamicResourceExtensionTests.cs | 6 ++- 6 files changed, 81 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 75c4b94174..f377eb848c 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -60,6 +60,7 @@ namespace Avalonia public static readonly StyledProperty ThemeProperty = AvaloniaProperty.Register(nameof(Theme)); + private static readonly ControlTheme s_invalidTheme = new ControlTheme(); private int _initCount; private string? _name; private readonly Classes _classes = new Classes(); @@ -72,6 +73,7 @@ namespace Avalonia private ITemplatedControl? _templatedParent; private bool _dataContextUpdating; private bool _hasPromotedTheme; + private ControlTheme? _implicitTheme; /// /// Initializes static members of the class. @@ -495,6 +497,31 @@ 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.StyleApplied(IStyleInstance instance) { instance = instance ?? throw new ArgumentNullException(nameof(instance)); @@ -736,6 +763,7 @@ namespace Avalonia if (_logicalRoot != null) { _logicalRoot = null; + _implicitTheme = null; DetachStyles(); OnDetachedFromLogicalTree(e); DetachedFromLogicalTree?.Invoke(this, e); diff --git a/src/Avalonia.Base/Styling/IStyleable.cs b/src/Avalonia.Base/Styling/IStyleable.cs index 61fcbdf850..254da4d85c 100644 --- a/src/Avalonia.Base/Styling/IStyleable.cs +++ b/src/Avalonia.Base/Styling/IStyleable.cs @@ -27,9 +27,9 @@ namespace Avalonia.Styling ITemplatedControl? TemplatedParent { get; } /// - /// Gets the theme to be applied to the control. + /// Gets the effective theme for the control as used by the syling system. /// - public ControlTheme? Theme { get; } + ControlTheme? GetEffectiveTheme(); /// /// Notifies the element that a style has been applied. diff --git a/src/Avalonia.Base/Styling/Styler.cs b/src/Avalonia.Base/Styling/Styler.cs index c9ea123bdc..6ac2e8d372 100644 --- a/src/Avalonia.Base/Styling/Styler.cs +++ b/src/Avalonia.Base/Styling/Styler.cs @@ -11,10 +11,10 @@ namespace Avalonia.Styling // If the control has a themed templated parent then first apply the styles from // the templated parent theme. if (target.TemplatedParent is IStyleable styleableParent) - styleableParent.Theme?.TryAttach(target, styleableParent); + styleableParent.GetEffectiveTheme()?.TryAttach(target, styleableParent); // Next apply the control theme. - target.Theme?.TryAttach(target, target); + target.GetEffectiveTheme()?.TryAttach(target, target); // Apply styles from the rest of the tree. if (target is IStyleHost styleHost) diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs index ab6c239393..522937b669 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs @@ -158,6 +158,49 @@ public class StyledElementTests_Theming } } + public class ImplicitTheme + { + [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); + + var border = Assert.IsType(target.VisualChild); + Assert.Equal(Brushes.Red, border.Background); + + target.Classes.Add("foo"); + Assert.Equal(Brushes.Green, border.Background); + } + + [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()); + + root.Child = null; + + Assert.Null(((IStyleable)target).GetEffectiveTheme()); + } + + private static ThemedControl CreateTarget() => new ThemedControl(); + + private static TestRoot CreateRoot(IControl child) + { + var result = new TestRoot(); + result.Resources.Add(typeof(ThemedControl), CreateTheme()); + result.Child = child; + result.LayoutManager.ExecuteInitialLayoutPass(); + return result; + } + } + public class ThemeFromStyle { [Fact] diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs index ca59fe8480..75e21a7138 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs @@ -137,9 +137,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters get { throw new NotImplementedException(); } } - public ControlTheme Theme + public ControlTheme GetEffectiveTheme() { - get { throw new NotImplementedException(); } + throw new NotImplementedException(); } public void DetachStyles() diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index 592dbfc0d1..987725c314 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -845,7 +845,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal("bar", border.Tag); var resourceProvider = (TrackingResourceProvider)window.Resources.MergedDictionaries[0]; - Assert.Equal(new[] { "bar" }, resourceProvider.RequestedResources); + Assert.Contains("bar", resourceProvider.RequestedResources); + Assert.DoesNotContain("foo", resourceProvider.RequestedResources); } [Fact] @@ -883,7 +884,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal("bar", border.Tag); var resourceProvider = (TrackingResourceProvider)window.Resources.MergedDictionaries[0]; - Assert.Equal(new[] { "bar" }, resourceProvider.RequestedResources); + Assert.Contains("bar", resourceProvider.RequestedResources); + Assert.DoesNotContain("foo", resourceProvider.RequestedResources); } private IDisposable StyledWindow(params (string, string)[] assets) From 8b4cf63be3ffbf29427bb16d15ff514c595d348b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 10 Jun 2022 11:18:22 +0200 Subject: [PATCH 024/240] Additional validation for ControlTheme children. --- src/Avalonia.Base/Styling/ChildSelector.cs | 2 +- .../Styling/DescendentSelector.cs | 2 +- src/Avalonia.Base/Styling/NestingSelector.cs | 2 +- src/Avalonia.Base/Styling/NotSelector.cs | 2 +- src/Avalonia.Base/Styling/NthChildSelector.cs | 2 +- src/Avalonia.Base/Styling/OrSelector.cs | 12 +--- .../Styling/PropertyEqualsSelector.cs | 2 +- src/Avalonia.Base/Styling/Selector.cs | 31 ++++++++- src/Avalonia.Base/Styling/Style.cs | 9 ++- src/Avalonia.Base/Styling/Styles.cs | 5 ++ src/Avalonia.Base/Styling/TemplateSelector.cs | 2 +- .../Styling/TypeNameAndClassSelector.cs | 2 +- .../Styling/ControlThemeTests.cs | 64 +++++++++++++++++++ 13 files changed, 117 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Base/Styling/ChildSelector.cs b/src/Avalonia.Base/Styling/ChildSelector.cs index 34f3a76b61..9512dc34df 100644 --- a/src/Avalonia.Base/Styling/ChildSelector.cs +++ b/src/Avalonia.Base/Styling/ChildSelector.cs @@ -65,6 +65,6 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => null; - internal override bool HasValidNestingSelector() => _parent.HasValidNestingSelector(); + protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/DescendentSelector.cs b/src/Avalonia.Base/Styling/DescendentSelector.cs index 4ffaff6861..677a924189 100644 --- a/src/Avalonia.Base/Styling/DescendentSelector.cs +++ b/src/Avalonia.Base/Styling/DescendentSelector.cs @@ -70,6 +70,6 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => null; - internal override bool HasValidNestingSelector() => _parent.HasValidNestingSelector(); + protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/NestingSelector.cs b/src/Avalonia.Base/Styling/NestingSelector.cs index 4393d3239f..77c5b719c6 100644 --- a/src/Avalonia.Base/Styling/NestingSelector.cs +++ b/src/Avalonia.Base/Styling/NestingSelector.cs @@ -33,6 +33,6 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => null; - internal override bool HasValidNestingSelector() => true; + protected override Selector? MovePreviousOrParent() => null; } } diff --git a/src/Avalonia.Base/Styling/NotSelector.cs b/src/Avalonia.Base/Styling/NotSelector.cs index 76a0690e96..c7727bb6b8 100644 --- a/src/Avalonia.Base/Styling/NotSelector.cs +++ b/src/Avalonia.Base/Styling/NotSelector.cs @@ -67,6 +67,6 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => _previous; - internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false; + protected override Selector? MovePreviousOrParent() => _previous; } } diff --git a/src/Avalonia.Base/Styling/NthChildSelector.cs b/src/Avalonia.Base/Styling/NthChildSelector.cs index 047bf434da..f473791664 100644 --- a/src/Avalonia.Base/Styling/NthChildSelector.cs +++ b/src/Avalonia.Base/Styling/NthChildSelector.cs @@ -105,7 +105,7 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => _previous; - internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false; + protected override Selector? MovePreviousOrParent() => _previous; public override string ToString() { diff --git a/src/Avalonia.Base/Styling/OrSelector.cs b/src/Avalonia.Base/Styling/OrSelector.cs index 913c27bf0c..af9249864f 100644 --- a/src/Avalonia.Base/Styling/OrSelector.cs +++ b/src/Avalonia.Base/Styling/OrSelector.cs @@ -103,18 +103,12 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => null; + protected override Selector? MovePreviousOrParent() => null; - internal override bool HasValidNestingSelector() + internal override void ValidateNestingSelector(bool inControlTheme) { foreach (var selector in _selectors) - { - if (!selector.HasValidNestingSelector()) - { - return false; - } - } - - return true; + selector.ValidateNestingSelector(inControlTheme); } private Type? EvaluateTargetType() diff --git a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs index 7a37daf087..48136ba2de 100644 --- a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs @@ -90,7 +90,7 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => _previous; - internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false; + protected override Selector? MovePreviousOrParent() => _previous; internal static bool Compare(Type propertyType, object? propertyValue, object? value) { diff --git a/src/Avalonia.Base/Styling/Selector.cs b/src/Avalonia.Base/Styling/Selector.cs index 1e06f3d375..7ce17518dd 100644 --- a/src/Avalonia.Base/Styling/Selector.cs +++ b/src/Avalonia.Base/Styling/Selector.cs @@ -86,7 +86,36 @@ namespace Avalonia.Styling /// protected abstract Selector? MovePrevious(); - internal abstract bool HasValidNestingSelector(); + /// + /// Moves to the previous selector or the parent selector. + /// + protected abstract Selector? MovePreviousOrParent(); + + internal virtual void ValidateNestingSelector(bool inControlTheme) + { + var s = this; + var templateCount = 0; + + do + { + if (inControlTheme) + { + if (!s.InTemplate && s.IsCombinator) + throw new InvalidOperationException( + "ControlTheme style may not directly contain a child or descendent selector."); + if (s is TemplateSelector && templateCount++ > 0) + throw new InvalidOperationException( + "ControlTemplate styles cannot contain multiple template selectors."); + } + + var previous = s.MovePreviousOrParent(); + + if (previous is null && s is not NestingSelector) + throw new InvalidOperationException("Child styles must have a nesting selector."); + + s = previous; + } while (s is not null); + } private static SelectorMatch MatchUntilCombinator( IStyleable control, diff --git a/src/Avalonia.Base/Styling/Style.cs b/src/Avalonia.Base/Styling/Style.cs index 7a6b746488..77c4e62d29 100644 --- a/src/Avalonia.Base/Styling/Style.cs +++ b/src/Avalonia.Base/Styling/Style.cs @@ -77,8 +77,13 @@ namespace Avalonia.Styling { if (Selector is null) throw new InvalidOperationException("Child styles must have a selector."); - if (!Selector.HasValidNestingSelector()) - throw new InvalidOperationException("Child styles must have a nesting selector."); + Selector.ValidateNestingSelector(false); + } + else if (parent is ControlTheme) + { + if (Selector is null) + throw new InvalidOperationException("Child styles must have a selector."); + Selector.ValidateNestingSelector(true); } base.SetParent(parent); diff --git a/src/Avalonia.Base/Styling/Styles.cs b/src/Avalonia.Base/Styling/Styles.cs index 4c011f1b0d..3a27275438 100644 --- a/src/Avalonia.Base/Styling/Styles.cs +++ b/src/Avalonia.Base/Styling/Styles.cs @@ -26,6 +26,11 @@ namespace Avalonia.Styling { _styles.ResetBehavior = ResetBehavior.Remove; _styles.CollectionChanged += OnCollectionChanged; + _styles.Validate = i => + { + if (i is ControlTheme) + throw new InvalidOperationException("ControlThemes cannot be added to a Styles collection."); + }; } public Styles(IResourceHost owner) diff --git a/src/Avalonia.Base/Styling/TemplateSelector.cs b/src/Avalonia.Base/Styling/TemplateSelector.cs index b0a2dae8d6..278e24a203 100644 --- a/src/Avalonia.Base/Styling/TemplateSelector.cs +++ b/src/Avalonia.Base/Styling/TemplateSelector.cs @@ -49,6 +49,6 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => null; - internal override bool HasValidNestingSelector() => _parent?.HasValidNestingSelector() ?? false; + protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index 24d5d6bbbf..6681a7da36 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -140,7 +140,7 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => _previous; - internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false; + protected override Selector? MovePreviousOrParent() => _previous; private string BuildSelectorString() { diff --git a/tests/Avalonia.Base.UnitTests/Styling/ControlThemeTests.cs b/tests/Avalonia.Base.UnitTests/Styling/ControlThemeTests.cs index 93a0e6c2fd..7a27a02fc4 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/ControlThemeTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/ControlThemeTests.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Controls; +using Avalonia.Controls.Primitives; using Avalonia.Styling; using Xunit; @@ -7,6 +8,15 @@ namespace Avalonia.Base.UnitTests.Styling { public class ControlThemeTests { + [Fact] + public void ControlTheme_Cannot_Be_Added_To_Styles() + { + var target = new ControlTheme(typeof(Button)); + var styles = new Styles(); + + Assert.Throws(() => styles.Add(target)); + } + [Fact] public void ControlTheme_Cannot_Be_Added_To_Style_Children() { @@ -24,5 +34,59 @@ namespace Avalonia.Base.UnitTests.Styling Assert.Throws(() => other.Children.Add(target)); } + + [Fact] + public void Style_Without_Selector_Cannot_Be_Added_To_Children() + { + var target = new ControlTheme(typeof(Button)); + var child = new Style(); + + Assert.Throws(() => target.Children.Add(child)); + } + + [Fact] + public void Style_Without_Nesting_Selector_Cannot_Be_Added_To_Children() + { + var target = new ControlTheme(typeof(Button)); + var child = new Style(x => x.OfType /// The other geometry. /// A new representing the intersection. - IGeometryImpl Intersect(IGeometryImpl geometry); + IGeometryImpl? Intersect(IGeometryImpl geometry); /// /// Indicates whether the geometry's stroke contains the specified point. From 14a7ee7e319b7a26d156770fc2b6c0ccb5243a14 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Mon, 4 Jul 2022 15:07:23 +0200 Subject: [PATCH 037/240] Extend docs --- src/Avalonia.Base/Platform/IGeometryImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Platform/IGeometryImpl.cs b/src/Avalonia.Base/Platform/IGeometryImpl.cs index 8760f9ed8a..5826cfb2ff 100644 --- a/src/Avalonia.Base/Platform/IGeometryImpl.cs +++ b/src/Avalonia.Base/Platform/IGeometryImpl.cs @@ -38,7 +38,7 @@ namespace Avalonia.Platform /// Intersects the geometry with another geometry. /// /// The other geometry. - /// A new representing the intersection. + /// A new representing the intersection or null when the operation failed. IGeometryImpl? Intersect(IGeometryImpl geometry); /// From 4d23058c9b940bd993ba7e08536e4725c1deb14c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 4 Jul 2022 17:37:04 +0200 Subject: [PATCH 038/240] Don't run an unnecessary batch update. If there are no styles to detach, there's no reason to run a batch update. --- src/Avalonia.Base/StyledElement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 189d73c502..ecf5d95ffc 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -832,7 +832,7 @@ namespace Avalonia private void DetachStyles() { - if (_appliedStyles is object) + if (_appliedStyles?.Count > 0) { BeginBatchUpdate(); From 9ab31f60b40d676fbe1a876dbf70a5e498904ebf Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 5 Jul 2022 17:17:51 +0200 Subject: [PATCH 039/240] Fix button flyout toggle Expose OverlayDismissEventPassThrough on FlyoutBase --- src/Avalonia.Controls/Button.cs | 56 ++++++++++++++------- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 48 ++++++++++++++---- src/Avalonia.Controls/Primitives/Popup.cs | 2 +- 3 files changed, 77 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 6dba33516b..8e5d4e1e06 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Windows.Input; using Avalonia.Automation.Peers; @@ -281,24 +282,29 @@ namespace Avalonia.Controls /// protected override void OnKeyDown(KeyEventArgs e) { - if (e.Key == Key.Enter) + switch (e.Key) { - OnClick(); - e.Handled = true; - } - else if (e.Key == Key.Space) - { - if (ClickMode == ClickMode.Press) - { + case Key.Enter: OnClick(); + e.Handled = true; + break; + + case Key.Space: + { + if (ClickMode == ClickMode.Press) + { + OnClick(); + } + + IsPressed = true; + e.Handled = true; + break; } - IsPressed = true; - e.Handled = true; - } - else if (e.Key == Key.Escape && Flyout != null) - { - // If Flyout doesn't have focusable content, close the flyout here - Flyout.Hide(); + + case Key.Escape when Flyout != null: + // If Flyout doesn't have focusable content, close the flyout here + CloseFlyout(); + break; } base.OnKeyDown(e); @@ -327,7 +333,14 @@ namespace Avalonia.Controls { if (IsEffectivelyEnabled) { - OpenFlyout(); + if (_isFlyoutOpen) + { + CloseFlyout(); + } + else + { + OpenFlyout(); + } var e = new RoutedEventArgs(ClickEvent); RaiseEvent(e); @@ -348,6 +361,14 @@ namespace Avalonia.Controls Flyout?.ShowAt(this); } + /// + /// Closes the button's flyout. + /// + protected virtual void CloseFlyout() + { + Flyout?.Hide(); + } + /// /// Invoked when the button's flyout is opened. /// @@ -494,8 +515,7 @@ namespace Avalonia.Controls // If flyout is changed while one is already open, make sure we // close the old one first - if (oldFlyout != null && - oldFlyout.IsOpen) + if (oldFlyout != null && oldFlyout.IsOpen) { oldFlyout.Hide(); } diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 1504d2b25f..a0f3407b7a 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -12,11 +12,6 @@ namespace Avalonia.Controls.Primitives { public abstract class FlyoutBase : AvaloniaObject, IPopupHostProvider { - static FlyoutBase() - { - Control.ContextFlyoutProperty.Changed.Subscribe(OnContextFlyoutPropertyChanged); - } - /// /// Defines the property /// @@ -49,6 +44,12 @@ namespace Avalonia.Controls.Primitives public static readonly AttachedProperty AttachedFlyoutProperty = AvaloniaProperty.RegisterAttached("AttachedFlyout", null); + /// + /// Defines the OverlayDismissEventPassThrough property + /// + public static readonly StyledProperty OverlayDismissEventPassThroughProperty = + Popup.OverlayDismissEventPassThroughProperty.AddOwner(); + private readonly Lazy _popupLazy; private bool _isOpen; private Control? _target; @@ -58,6 +59,12 @@ namespace Avalonia.Controls.Primitives private IDisposable? _transientDisposable; private Action? _popupHostChangedHandler; + static FlyoutBase() + { + OverlayDismissEventPassThroughProperty.OverrideDefaultValue(true); + Control.ContextFlyoutProperty.Changed.Subscribe(OnContextFlyoutPropertyChanged); + } + public FlyoutBase() { _popupLazy = new Lazy(() => CreatePopup()); @@ -101,6 +108,21 @@ namespace Avalonia.Controls.Primitives private set => SetAndRaise(TargetProperty, ref _target, value); } + /// + /// Gets or sets a value indicating whether the event that closes the flyout is passed + /// through to the parent window. + /// + /// + /// Clicks outside the the flyout cause the flyout to close. When is set to + /// false, these clicks will be handled by the flyout and not be registered by the parent + /// window. When set to true, the events will be passed through to the parent window. + /// + public bool OverlayDismissEventPassThrough + { + get => GetValue(OverlayDismissEventPassThroughProperty); + set => SetValue(OverlayDismissEventPassThroughProperty, value); + } + IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host; event Action? IPopupHostProvider.PopupHostChanged @@ -175,6 +197,8 @@ namespace Avalonia.Controls.Primitives IsOpen = false; Popup.IsOpen = false; + Popup.OverlayInputPassThroughElement = null; + ((ISetLogicalParent)Popup).SetParent(null); // Ensure this isn't active @@ -231,6 +255,9 @@ namespace Avalonia.Controls.Primitives Popup.Child = CreatePresenter(); } + Popup.OverlayInputPassThroughElement = placementTarget; + Popup.OverlayDismissEventPassThrough = OverlayDismissEventPassThrough; + if (CancelOpening()) { return false; @@ -356,10 +383,11 @@ namespace Avalonia.Controls.Primitives private Popup CreatePopup() { - var popup = new Popup(); - popup.WindowManagerAddShadowHint = false; - popup.IsLightDismissEnabled = true; - popup.OverlayDismissEventPassThrough = true; + var popup = new Popup + { + WindowManagerAddShadowHint = false, + IsLightDismissEnabled = true + }; popup.Opened += OnPopupOpened; popup.Closed += OnPopupClosed; @@ -372,7 +400,7 @@ namespace Avalonia.Controls.Primitives { IsOpen = true; - _popupHostChangedHandler?.Invoke(Popup!.Host); + _popupHostChangedHandler?.Invoke(Popup.Host); } private void OnPopupClosing(object? sender, CancelEventArgs e) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 1501d97470..3573ad9aaa 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -501,7 +501,7 @@ namespace Avalonia.Controls.Primitives if (dismissLayer != null) { dismissLayer.IsVisible = true; - dismissLayer.InputPassThroughElement = _overlayInputPassThroughElement; + dismissLayer.InputPassThroughElement = OverlayInputPassThroughElement; Disposable.Create(() => { From 030c956e31d77e2572088b94daf69a8f0ec317a6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 5 Jul 2022 18:31:49 +0200 Subject: [PATCH 040/240] Revert "Make MovePreviousOrParent internal as well." This reverts commit 5e1f28f2a9e3a4f8606bf8efb6aa393d12f62d45. --- src/Avalonia.Base/Styling/ChildSelector.cs | 2 +- src/Avalonia.Base/Styling/DescendentSelector.cs | 2 +- src/Avalonia.Base/Styling/NestingSelector.cs | 2 +- src/Avalonia.Base/Styling/NotSelector.cs | 2 +- src/Avalonia.Base/Styling/NthChildSelector.cs | 2 +- src/Avalonia.Base/Styling/OrSelector.cs | 2 +- src/Avalonia.Base/Styling/PropertyEqualsSelector.cs | 2 +- src/Avalonia.Base/Styling/Selector.cs | 2 +- src/Avalonia.Base/Styling/TemplateSelector.cs | 2 +- src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Base/Styling/ChildSelector.cs b/src/Avalonia.Base/Styling/ChildSelector.cs index b3f6794154..bc1d257ce6 100644 --- a/src/Avalonia.Base/Styling/ChildSelector.cs +++ b/src/Avalonia.Base/Styling/ChildSelector.cs @@ -65,6 +65,6 @@ namespace Avalonia.Styling } private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (null, null); - private protected override Selector? MovePreviousOrParent() => _parent; + protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/DescendentSelector.cs b/src/Avalonia.Base/Styling/DescendentSelector.cs index 7f36c27a37..3a16574e04 100644 --- a/src/Avalonia.Base/Styling/DescendentSelector.cs +++ b/src/Avalonia.Base/Styling/DescendentSelector.cs @@ -70,6 +70,6 @@ namespace Avalonia.Styling } private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (null, null); - private protected override Selector? MovePreviousOrParent() => _parent; + protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/NestingSelector.cs b/src/Avalonia.Base/Styling/NestingSelector.cs index 60ad8106d9..dd0bac31c6 100644 --- a/src/Avalonia.Base/Styling/NestingSelector.cs +++ b/src/Avalonia.Base/Styling/NestingSelector.cs @@ -37,6 +37,6 @@ namespace Avalonia.Styling return parent is Style parentStyle ? (parentStyle.Selector, parentStyle.Parent) : (null, null); } - private protected override Selector? MovePreviousOrParent() => null; + protected override Selector? MovePreviousOrParent() => null; } } diff --git a/src/Avalonia.Base/Styling/NotSelector.cs b/src/Avalonia.Base/Styling/NotSelector.cs index be5cfaca49..ebde392a3b 100644 --- a/src/Avalonia.Base/Styling/NotSelector.cs +++ b/src/Avalonia.Base/Styling/NotSelector.cs @@ -67,6 +67,6 @@ namespace Avalonia.Styling } private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (_previous, nestingParent); - private protected override Selector? MovePreviousOrParent() => _previous; + protected override Selector? MovePreviousOrParent() => _previous; } } diff --git a/src/Avalonia.Base/Styling/NthChildSelector.cs b/src/Avalonia.Base/Styling/NthChildSelector.cs index 40fb5a4434..e6d9cf58a9 100644 --- a/src/Avalonia.Base/Styling/NthChildSelector.cs +++ b/src/Avalonia.Base/Styling/NthChildSelector.cs @@ -105,7 +105,7 @@ namespace Avalonia.Styling } private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (_previous, nestingParent); - private protected override Selector? MovePreviousOrParent() => _previous; + protected override Selector? MovePreviousOrParent() => _previous; public override string ToString() { diff --git a/src/Avalonia.Base/Styling/OrSelector.cs b/src/Avalonia.Base/Styling/OrSelector.cs index 2ab00d65f8..115e0aeb95 100644 --- a/src/Avalonia.Base/Styling/OrSelector.cs +++ b/src/Avalonia.Base/Styling/OrSelector.cs @@ -103,7 +103,7 @@ namespace Avalonia.Styling } private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (null, null); - private protected override Selector? MovePreviousOrParent() => null; + protected override Selector? MovePreviousOrParent() => null; internal override void ValidateNestingSelector(bool inControlTheme) { diff --git a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs index 62caa9bab3..96f8c8dfeb 100644 --- a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs @@ -90,7 +90,7 @@ namespace Avalonia.Styling } private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (_previous, nestingParent); - private protected override Selector? MovePreviousOrParent() => _previous; + protected override Selector? MovePreviousOrParent() => _previous; internal static bool Compare(Type propertyType, object? propertyValue, object? value) { diff --git a/src/Avalonia.Base/Styling/Selector.cs b/src/Avalonia.Base/Styling/Selector.cs index 67109961ba..cc8598c5e3 100644 --- a/src/Avalonia.Base/Styling/Selector.cs +++ b/src/Avalonia.Base/Styling/Selector.cs @@ -95,7 +95,7 @@ namespace Avalonia.Styling /// /// Moves to the previous selector or the parent selector. /// - private protected abstract Selector? MovePreviousOrParent(); + protected abstract Selector? MovePreviousOrParent(); internal virtual void ValidateNestingSelector(bool inControlTheme) { diff --git a/src/Avalonia.Base/Styling/TemplateSelector.cs b/src/Avalonia.Base/Styling/TemplateSelector.cs index 0af263b4e5..a68b7003b8 100644 --- a/src/Avalonia.Base/Styling/TemplateSelector.cs +++ b/src/Avalonia.Base/Styling/TemplateSelector.cs @@ -49,6 +49,6 @@ namespace Avalonia.Styling } private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (null, null); - private protected override Selector? MovePreviousOrParent() => _parent; + protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index 7783d3f653..3a2150b1e9 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -140,7 +140,7 @@ namespace Avalonia.Styling } private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (_previous, nestingParent); - private protected override Selector? MovePreviousOrParent() => _previous; + protected override Selector? MovePreviousOrParent() => _previous; private string BuildSelectorString() { From a341c33b5526c1b2fc886db3d07025e6fbb8110d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 5 Jul 2022 18:58:38 +0200 Subject: [PATCH 041/240] Revert "Fix parent selectors with /template/ at end." This reverts commit e50b416d5bf1529544830d5a291a1fb2f1b0da64 as it was not functioning correctly. --- src/Avalonia.Base/Styling/ChildSelector.cs | 2 +- src/Avalonia.Base/Styling/DescendentSelector.cs | 2 +- src/Avalonia.Base/Styling/NestingSelector.cs | 8 ++------ src/Avalonia.Base/Styling/NotSelector.cs | 2 +- src/Avalonia.Base/Styling/NthChildSelector.cs | 2 +- src/Avalonia.Base/Styling/OrSelector.cs | 2 +- src/Avalonia.Base/Styling/PropertyEqualsSelector.cs | 2 +- src/Avalonia.Base/Styling/Selector.cs | 12 +++--------- src/Avalonia.Base/Styling/TemplateSelector.cs | 2 +- .../Styling/TypeNameAndClassSelector.cs | 2 +- 10 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Base/Styling/ChildSelector.cs b/src/Avalonia.Base/Styling/ChildSelector.cs index bc1d257ce6..9512dc34df 100644 --- a/src/Avalonia.Base/Styling/ChildSelector.cs +++ b/src/Avalonia.Base/Styling/ChildSelector.cs @@ -64,7 +64,7 @@ namespace Avalonia.Styling } } - private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (null, null); + protected override Selector? MovePrevious() => null; protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/DescendentSelector.cs b/src/Avalonia.Base/Styling/DescendentSelector.cs index 3a16574e04..677a924189 100644 --- a/src/Avalonia.Base/Styling/DescendentSelector.cs +++ b/src/Avalonia.Base/Styling/DescendentSelector.cs @@ -69,7 +69,7 @@ namespace Avalonia.Styling } } - private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (null, null); + protected override Selector? MovePrevious() => null; protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/NestingSelector.cs b/src/Avalonia.Base/Styling/NestingSelector.cs index dd0bac31c6..77c5b719c6 100644 --- a/src/Avalonia.Base/Styling/NestingSelector.cs +++ b/src/Avalonia.Base/Styling/NestingSelector.cs @@ -17,7 +17,7 @@ namespace Avalonia.Styling { if (parent is Style s && s.Selector is not null) { - return SelectorMatch.AlwaysThisType; + return s.Selector.Match(control, s.Parent, subscribe); } else if (parent is ControlTheme theme) { @@ -32,11 +32,7 @@ namespace Avalonia.Styling "Nesting selector was specified but cannot determine parent selector."); } - private protected override (Selector?, IStyle?) MovePrevious(IStyle? parent) - { - return parent is Style parentStyle ? (parentStyle.Selector, parentStyle.Parent) : (null, null); - } - + protected override Selector? MovePrevious() => null; protected override Selector? MovePreviousOrParent() => null; } } diff --git a/src/Avalonia.Base/Styling/NotSelector.cs b/src/Avalonia.Base/Styling/NotSelector.cs index ebde392a3b..c7727bb6b8 100644 --- a/src/Avalonia.Base/Styling/NotSelector.cs +++ b/src/Avalonia.Base/Styling/NotSelector.cs @@ -66,7 +66,7 @@ namespace Avalonia.Styling } } - private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (_previous, nestingParent); + protected override Selector? MovePrevious() => _previous; protected override Selector? MovePreviousOrParent() => _previous; } } diff --git a/src/Avalonia.Base/Styling/NthChildSelector.cs b/src/Avalonia.Base/Styling/NthChildSelector.cs index e6d9cf58a9..f473791664 100644 --- a/src/Avalonia.Base/Styling/NthChildSelector.cs +++ b/src/Avalonia.Base/Styling/NthChildSelector.cs @@ -104,7 +104,7 @@ namespace Avalonia.Styling return match ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; } - private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (_previous, nestingParent); + protected override Selector? MovePrevious() => _previous; protected override Selector? MovePreviousOrParent() => _previous; public override string ToString() diff --git a/src/Avalonia.Base/Styling/OrSelector.cs b/src/Avalonia.Base/Styling/OrSelector.cs index 115e0aeb95..af9249864f 100644 --- a/src/Avalonia.Base/Styling/OrSelector.cs +++ b/src/Avalonia.Base/Styling/OrSelector.cs @@ -102,7 +102,7 @@ namespace Avalonia.Styling } } - private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (null, null); + protected override Selector? MovePrevious() => null; protected override Selector? MovePreviousOrParent() => null; internal override void ValidateNestingSelector(bool inControlTheme) diff --git a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs index 96f8c8dfeb..48136ba2de 100644 --- a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs @@ -89,7 +89,7 @@ namespace Avalonia.Styling } - private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (_previous, nestingParent); + protected override Selector? MovePrevious() => _previous; protected override Selector? MovePreviousOrParent() => _previous; internal static bool Compare(Type propertyType, object? propertyValue, object? value) diff --git a/src/Avalonia.Base/Styling/Selector.cs b/src/Avalonia.Base/Styling/Selector.cs index cc8598c5e3..7ce17518dd 100644 --- a/src/Avalonia.Base/Styling/Selector.cs +++ b/src/Avalonia.Base/Styling/Selector.cs @@ -84,13 +84,7 @@ namespace Avalonia.Styling /// /// Moves to the previous selector. /// - /// - /// The parent style, if the selector is on a nested style. - /// - /// - /// The previous selector, and its nesting parent. - /// - private protected abstract (Selector?, IStyle?) MovePrevious(IStyle? nestingParent); + protected abstract Selector? MovePrevious(); /// /// Moves to the previous selector or the parent selector. @@ -148,14 +142,14 @@ namespace Avalonia.Styling ref AndActivatorBuilder activators, ref Selector? combinator) { - var (previous, previousParent) = selector.MovePrevious(parent); + var previous = selector.MovePrevious(); // Selectors are stored from right-to-left, so we recurse into the selector in order to // reverse this order, because the type selector will be on the left and is our best // opportunity to exit early. if (previous != null && !previous.IsCombinator) { - var previousMatch = Match(control, previous, previousParent, subscribe, ref activators, ref combinator); + var previousMatch = Match(control, previous, parent, subscribe, ref activators, ref combinator); if (previousMatch < SelectorMatchResult.Sometimes) { diff --git a/src/Avalonia.Base/Styling/TemplateSelector.cs b/src/Avalonia.Base/Styling/TemplateSelector.cs index a68b7003b8..278e24a203 100644 --- a/src/Avalonia.Base/Styling/TemplateSelector.cs +++ b/src/Avalonia.Base/Styling/TemplateSelector.cs @@ -48,7 +48,7 @@ namespace Avalonia.Styling return _parent.Match(templatedParent, parent, subscribe); } - private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (null, null); + protected override Selector? MovePrevious() => null; protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index 3a2150b1e9..6681a7da36 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -139,7 +139,7 @@ namespace Avalonia.Styling return Name == null ? SelectorMatch.AlwaysThisType : SelectorMatch.AlwaysThisInstance; } - private protected override (Selector?, IStyle?) MovePrevious(IStyle? nestingParent) => (_previous, nestingParent); + protected override Selector? MovePrevious() => _previous; protected override Selector? MovePreviousOrParent() => _previous; private string BuildSelectorString() From 3e17bd066240ef2661ea305b7c16be78ac090877 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 6 Jul 2022 10:06:39 +0200 Subject: [PATCH 042/240] Skip these failing tests for now. I will work on a fix. --- .../Styling/SelectorTests_Nesting.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs b/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs index a60d21a018..747ad585e7 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs @@ -149,8 +149,8 @@ namespace Avalonia.Base.UnitTests.Styling control.Classes.Remove("foo"); Assert.False(sink.Active); } - - [Fact] + + [Fact(Skip = "Template selectors a the end of nesting parent selectors currently broken")] public void Template_Nesting_OfType_Matches() { var control = new Control1 { Classes = { "foo" } }; @@ -198,7 +198,7 @@ namespace Avalonia.Base.UnitTests.Styling Assert.Equal(SelectorMatchResult.Sometimes, match.Result); } - [Fact] + [Fact(Skip = "Template selectors a the end of nesting parent selectors currently broken")] public void Class_Template_Nesting_OfType_Matches() { var control = new Control1 { Classes = { "foo" } }; From 97a5a9e1f64110d01b8d8bf3937a3518f6fd567b Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 6 Jul 2022 14:24:55 -0400 Subject: [PATCH 043/240] Add folder.GetItemsAsync API --- .../ControlCatalog/Pages/DialogsPage.xaml.cs | 16 ++++++- .../Platform/Storage/AndroidStorageItem.cs | 25 +++++++++++ .../Storage/FileIO/BclStorageFolder.cs | 12 +++++ .../Platform/Storage/IStorageFolder.cs | 11 ++++- .../Interop/Storage/StorageProviderInterop.cs | 25 +++++++++++ .../Interop/Typescript/StorageProvider.ts | 45 +++++++++++++------ .../Avalonia.iOS/Storage/IOSStorageItem.cs | 17 +++++++ 7 files changed, 134 insertions(+), 17 deletions(-) diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index f7b6db1255..e13c2052eb 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -243,8 +243,8 @@ namespace ControlCatalog.Pages async Task SetPickerResult(IReadOnlyCollection? items) { items ??= Array.Empty(); - var mappedResults = items.Select(FullPathOrName).ToList(); bookmarkContainer.Text = items.FirstOrDefault(f => f.CanBookmark) is { } f ? await f.SaveBookmark() : "Can't bookmark"; + var mappedResults = new List(); if (items.FirstOrDefault() is IStorageItem item) { @@ -293,7 +293,19 @@ Content: lastSelectedDirectory = await item.GetParentAsync(); if (lastSelectedDirectory is not null) { - mappedResults.Insert(0, "Parent: " + FullPathOrName(lastSelectedDirectory)); + mappedResults.Add(FullPathOrName(lastSelectedDirectory)); + } + + foreach (var selectedItem in items) + { + mappedResults.Add("+> " + FullPathOrName(selectedItem)); + if (selectedItem is IStorageFolder folder) + { + foreach (var innerItems in await folder.GetItemsAsync()) + { + mappedResults.Add("++> " + FullPathOrName(innerItems)); + } + } } } diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs index 50581d47b1..1e81642e15 100644 --- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs +++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -106,6 +107,30 @@ internal sealed class AndroidStorageFolder : AndroidStorageItem, IStorageBookmar { return Task.FromResult(new StorageItemProperties()); } + + public async Task> GetItemsAsync() + { + using var javaFile = new JavaFile(Uri.Path!); + + // Java file represents files AND directories. Don't be confused. + var files = await javaFile.ListFilesAsync().ConfigureAwait(false); + if (files is null) + { + return Array.Empty(); + } + + return files + .Select(f => (file: f, uri: AndroidUri.FromFile(f))) + .Where(t => t.uri is not null) + .Select(t => t.file switch + { + { IsFile: true } => (IStorageItem)new AndroidStorageFile(Context, t.uri!), + { IsDirectory: true } => new AndroidStorageFolder(Context, t.uri!), + _ => null + }) + .Where(i => i is not null) + .ToArray()!; + } } internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkFile diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs index 7267017eaf..0a22f4bd03 100644 --- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs +++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using System.Security; using System.Threading.Tasks; using Avalonia.Metadata; @@ -43,6 +45,16 @@ public class BclStorageFolder : IStorageBookmarkFolder return Task.FromResult(null); } + public Task> GetItemsAsync() + { + var items = _directoryInfo.GetDirectories() + .Select(d => (IStorageItem)new BclStorageFolder(d)) + .Concat(_directoryInfo.GetFiles().Select(f => new BclStorageFile(f))) + .ToArray(); + + return Task.FromResult>(items); + } + public virtual Task SaveBookmark() { return Task.FromResult(_directoryInfo.FullName); diff --git a/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs b/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs index 25b9f01a92..0ffb9f41c6 100644 --- a/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs +++ b/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs @@ -1,4 +1,6 @@ -using Avalonia.Metadata; +using System.Collections.Generic; +using System.Threading.Tasks; +using Avalonia.Metadata; namespace Avalonia.Platform.Storage; @@ -8,4 +10,11 @@ namespace Avalonia.Platform.Storage; [NotClientImplementable] public interface IStorageFolder : IStorageItem { + /// + /// Gets the files and subfolders in the current folder. + /// + /// + /// When this method completes successfully, it returns a list of the files and folders in the current folder. Each item in the list is represented by an implementation object. + /// + Task> GetItemsAsync(); } diff --git a/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs index 14dc53d7b5..129463774c 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs +++ b/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs @@ -196,5 +196,30 @@ namespace Avalonia.Web.Blazor.Interop.Storage public JSStorageFolder(IJSInProcessObjectReference fileHandle) : base(fileHandle) { } + + public async Task> GetItemsAsync() + { + var items = await FileHandle.InvokeAsync("getItems"); + if (items is null) + { + return Array.Empty(); + } + + var count = items.Invoke("count"); + + return Enumerable.Range(0, count) + .Select(index => + { + var reference = items.Invoke("at", index); + return reference.Invoke("getKind") switch + { + "directory" => (IStorageItem)new JSStorageFolder(reference), + "file" => new JSStorageFile(reference), + _ => null + }; + }) + .Where(i => i is not null) + .ToArray()!; + } } } diff --git a/src/Web/Avalonia.Web.Blazor/Interop/Typescript/StorageProvider.ts b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/StorageProvider.ts index c32eef3226..aee74b9067 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/Typescript/StorageProvider.ts +++ b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/StorageProvider.ts @@ -14,6 +14,8 @@ declare global { queryPermission(options?: { mode: PermissionsMode }): Promise<"granted" | "denied" | "prompt">; requestPermission(options?: { mode: PermissionsMode }): Promise<"granted" | "denied" | "prompt">; + + entries(): AsyncIterableIterator<[string, FileSystemFileHandle]>; } type WellKnownDirectory = "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; type StartInDirectory = WellKnownDirectory | FileSystemFileHandle; @@ -53,7 +55,7 @@ class IndexedDbWrapper { } public connect(): Promise { - var conn = window.indexedDB.open(this.databaseName, 1); + const conn = window.indexedDB.open(this.databaseName, 1); conn.onupgradeneeded = event => { const db = (>event.target).result; @@ -85,7 +87,7 @@ class InnerDbConnection { const os = this.openStore(store, "readwrite"); return new Promise((resolve, reject) => { - var response = os.put(obj, key); + const response = os.put(obj, key); response.onsuccess = () => { resolve(response.result); }; @@ -99,7 +101,7 @@ class InnerDbConnection { const os = this.openStore(store, "readonly"); return new Promise((resolve, reject) => { - var response = os.get(key); + const response = os.get(key); response.onsuccess = () => { resolve(response.result); }; @@ -113,7 +115,7 @@ class InnerDbConnection { const os = this.openStore(store, "readwrite"); return new Promise((resolve, reject) => { - var response = os.delete(key); + const response = os.delete(key); response.onsuccess = () => { resolve(); }; @@ -134,17 +136,20 @@ const avaloniaDb = new IndexedDbWrapper("AvaloniaDb", [ ]) class StorageItem { - constructor(private handle: FileSystemFileHandle, private bookmarkId?: string) { } + constructor(public handle: FileSystemFileHandle, private bookmarkId?: string) { } public getName(): string { return this.handle.name } + public getKind(): string { + return this.handle.kind; + } + public async openRead(): Promise { await this.verityPermissions('read'); - var file = await this.handle.getFile(); - return file; + return await this.handle.getFile(); } public async openWrite(): Promise { @@ -154,7 +159,7 @@ class StorageItem { } public async getProperties(): Promise<{ Size: number, LastModified: number, Type: string }> { - var file = this.handle.getFile && await this.handle.getFile(); + const file = this.handle.getFile && await this.handle.getFile(); return file && { Size: file.size, @@ -163,6 +168,18 @@ class StorageItem { } } + public async getItems(): Promise { + if (this.handle.kind !== "directory"){ + return new StorageItems([]); + } + + const items: StorageItem[] = []; + for await (const [key, value] of this.handle.entries()) { + items.push(new StorageItem(value)); + } + return new StorageItems(items); + } + private async verityPermissions(mode: PermissionsMode): Promise { if (await this.handle.queryPermission({ mode }) === 'granted') { return; @@ -235,12 +252,12 @@ export class StorageProvider { } public static async selectFolderDialog( - startIn: StartInDirectory | null) + startIn: StorageItem | null) : Promise { // 'Picker' API doesn't accept "null" as a parameter, so it should be set to undefined. const options: DirectoryPickerOptions = { - startIn: (startIn || undefined) + startIn: (startIn?.handle || undefined) }; const handle = await window.showDirectoryPicker(options); @@ -248,12 +265,12 @@ export class StorageProvider { } public static async openFileDialog( - startIn: StartInDirectory | null, multiple: boolean, + startIn: StorageItem | null, multiple: boolean, types: FilePickerAcceptType[] | null, excludeAcceptAllOption: boolean) : Promise { const options: OpenFilePickerOptions = { - startIn: (startIn || undefined), + startIn: (startIn?.handle || undefined), multiple, excludeAcceptAllOption, types: (types || undefined) @@ -264,12 +281,12 @@ export class StorageProvider { } public static async saveFileDialog( - startIn: StartInDirectory | null, suggestedName: string | null, + startIn: StorageItem | null, suggestedName: string | null, types: FilePickerAcceptType[] | null, excludeAcceptAllOption: boolean) : Promise { const options: SaveFilePickerOptions = { - startIn: (startIn || undefined), + startIn: (startIn?.handle || undefined), suggestedName: (suggestedName || undefined), excludeAcceptAllOption, types: (types || undefined) diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs index 6fb296d0e0..cfb2a497be 100644 --- a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs +++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using System.Threading.Tasks; using Avalonia.Logging; using Avalonia.Platform.Storage; @@ -118,4 +120,19 @@ internal sealed class IOSStorageFolder : IOSStorageItem, IStorageBookmarkFolder public IOSStorageFolder(NSUrl url) : base(url) { } + + public Task> GetItemsAsync() + { + var content = NSFileManager.DefaultManager.GetDirectoryContent(Url, null, NSDirectoryEnumerationOptions.None, out var error); + if (error is not null) + { + return Task.FromException>(new NSErrorException(error)); + } + + var items = content + .Select(u => u.HasDirectoryPath ? (IStorageItem)new IOSStorageFolder(u) : new IOSStorageFile(u)) + .ToArray(); + + return Task.FromResult>(items); + } } From 5a8c9f9c0904c83095291d9ae0c02abd8a9d259f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 6 Jul 2022 14:31:00 -0400 Subject: [PATCH 044/240] Use Async in naming --- samples/ControlCatalog/Pages/DialogsPage.xaml.cs | 10 +++++----- .../Platform/Storage/AndroidStorageItem.cs | 8 ++++---- .../Platform/Storage/FileIO/BclStorageFile.cs | 8 ++++---- .../Platform/Storage/FileIO/BclStorageFolder.cs | 4 ++-- .../Platform/Storage/IStorageBookmarkItem.cs | 2 +- src/Avalonia.Base/Platform/Storage/IStorageFile.cs | 4 ++-- src/Avalonia.Base/Platform/Storage/IStorageItem.cs | 2 +- .../Interop/Storage/StorageProviderInterop.cs | 8 ++++---- src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs | 8 ++++---- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index e13c2052eb..036dccde0e 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -195,10 +195,10 @@ namespace ControlCatalog.Pages { // Sync disposal of StreamWriter is not supported on WASM #if NET6_0_OR_GREATER - await using var stream = await file.OpenWrite(); + await using var stream = await file.OpenWriteAsync(); await using var reader = new System.IO.StreamWriter(stream); #else - using var stream = await file.OpenWrite(); + using var stream = await file.OpenWriteAsync(); using var reader = new System.IO.StreamWriter(stream); #endif await reader.WriteLineAsync(openedFileContent.Text); @@ -243,7 +243,7 @@ namespace ControlCatalog.Pages async Task SetPickerResult(IReadOnlyCollection? items) { items ??= Array.Empty(); - bookmarkContainer.Text = items.FirstOrDefault(f => f.CanBookmark) is { } f ? await f.SaveBookmark() : "Can't bookmark"; + bookmarkContainer.Text = items.FirstOrDefault(f => f.CanBookmark) is { } f ? await f.SaveBookmarkAsync() : "Can't bookmark"; var mappedResults = new List(); if (items.FirstOrDefault() is IStorageItem item) @@ -267,9 +267,9 @@ Content: if (file.CanOpenRead) { #if NET6_0_OR_GREATER - await using var stream = await file.OpenRead(); + await using var stream = await file.OpenReadAsync(); #else - using var stream = await file.OpenRead(); + using var stream = await file.OpenReadAsync(); #endif using var reader = new System.IO.StreamReader(stream); diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs index 1e81642e15..a9b2e16d43 100644 --- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs +++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs @@ -36,13 +36,13 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem public bool CanBookmark => true; - public Task SaveBookmark() + public Task SaveBookmarkAsync() { Context.ContentResolver?.TakePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission); return Task.FromResult(Uri.ToString()); } - public Task ReleaseBookmark() + public Task ReleaseBookmarkAsync() { Context.ContentResolver?.ReleasePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission); return Task.CompletedTask; @@ -143,10 +143,10 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF public bool CanOpenWrite => true; - public Task OpenRead() => Task.FromResult(OpenContentStream(Context, Uri, false) + public Task OpenReadAsync() => Task.FromResult(OpenContentStream(Context, Uri, false) ?? throw new InvalidOperationException("Failed to open content stream")); - public Task OpenWrite() => Task.FromResult(OpenContentStream(Context, Uri, true) + public Task OpenWriteAsync() => Task.FromResult(OpenContentStream(Context, Uri, true) ?? throw new InvalidOperationException("Failed to open content stream")); private Stream? OpenContentStream(Context context, AndroidUri uri, bool isOutput) diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs index 5af02219ce..cf21e9b8b5 100644 --- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs +++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs @@ -47,22 +47,22 @@ public class BclStorageFile : IStorageBookmarkFile return Task.FromResult(null); } - public Task OpenRead() + public Task OpenReadAsync() { return Task.FromResult(_fileInfo.OpenRead()); } - public Task OpenWrite() + public Task OpenWriteAsync() { return Task.FromResult(_fileInfo.OpenWrite()); } - public virtual Task SaveBookmark() + public virtual Task SaveBookmarkAsync() { return Task.FromResult(_fileInfo.FullName); } - public Task ReleaseBookmark() + public Task ReleaseBookmarkAsync() { // No-op return Task.CompletedTask; diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs index 0a22f4bd03..cd6c8be1ae 100644 --- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs +++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs @@ -55,12 +55,12 @@ public class BclStorageFolder : IStorageBookmarkFolder return Task.FromResult>(items); } - public virtual Task SaveBookmark() + public virtual Task SaveBookmarkAsync() { return Task.FromResult(_directoryInfo.FullName); } - public Task ReleaseBookmark() + public Task ReleaseBookmarkAsync() { // No-op return Task.CompletedTask; diff --git a/src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs b/src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs index d21c950862..40f2720ee8 100644 --- a/src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs +++ b/src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs @@ -6,7 +6,7 @@ namespace Avalonia.Platform.Storage; [NotClientImplementable] public interface IStorageBookmarkItem : IStorageItem { - Task ReleaseBookmark(); + Task ReleaseBookmarkAsync(); } [NotClientImplementable] diff --git a/src/Avalonia.Base/Platform/Storage/IStorageFile.cs b/src/Avalonia.Base/Platform/Storage/IStorageFile.cs index 965caf8216..46aa6efa72 100644 --- a/src/Avalonia.Base/Platform/Storage/IStorageFile.cs +++ b/src/Avalonia.Base/Platform/Storage/IStorageFile.cs @@ -18,7 +18,7 @@ public interface IStorageFile : IStorageItem /// /// Opens a stream for read access. /// - Task OpenRead(); + Task OpenReadAsync(); /// /// Returns true, if file is writeable. @@ -28,5 +28,5 @@ public interface IStorageFile : IStorageItem /// /// Opens stream for writing to the file. /// - Task OpenWrite(); + Task OpenWriteAsync(); } diff --git a/src/Avalonia.Base/Platform/Storage/IStorageItem.cs b/src/Avalonia.Base/Platform/Storage/IStorageItem.cs index 8513ebc7d9..f5469d31c9 100644 --- a/src/Avalonia.Base/Platform/Storage/IStorageItem.cs +++ b/src/Avalonia.Base/Platform/Storage/IStorageItem.cs @@ -44,7 +44,7 @@ public interface IStorageItem : IDisposable /// /// Returns identifier of a bookmark. Can be null if OS denied request. /// - Task SaveBookmark(); + Task SaveBookmarkAsync(); /// /// Gets the parent folder of the current storage item. diff --git a/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs index 129463774c..2bc46e97b5 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs +++ b/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs @@ -145,7 +145,7 @@ namespace Avalonia.Web.Blazor.Interop.Storage public bool CanBookmark => true; - public Task SaveBookmark() + public Task SaveBookmarkAsync() { return FileHandle.InvokeAsync("saveBookmark").AsTask(); } @@ -155,7 +155,7 @@ namespace Avalonia.Web.Blazor.Interop.Storage return Task.FromResult(null); } - public Task ReleaseBookmark() + public Task ReleaseBookmarkAsync() { return FileHandle.InvokeAsync("deleteBookmark").AsTask(); } @@ -174,7 +174,7 @@ namespace Avalonia.Web.Blazor.Interop.Storage } public bool CanOpenRead => true; - public async Task OpenRead() + public async Task OpenReadAsync() { var stream = await FileHandle.InvokeAsync("openRead"); // Remove maxAllowedSize limit, as developer can decide if they read only small part or everything. @@ -182,7 +182,7 @@ namespace Avalonia.Web.Blazor.Interop.Storage } public bool CanOpenWrite => true; - public async Task OpenWrite() + public async Task OpenWriteAsync() { var properties = await FileHandle.InvokeAsync("getProperties"); var streamWriter = await FileHandle.InvokeAsync("openWrite"); diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs index cfb2a497be..a801e83562 100644 --- a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs +++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs @@ -51,13 +51,13 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem return Task.FromResult(new IOSStorageFolder(Url.RemoveLastPathComponent())); } - public Task ReleaseBookmark() + public Task ReleaseBookmarkAsync() { // no-op return Task.CompletedTask; } - public Task SaveBookmark() + public Task SaveBookmarkAsync() { try { @@ -104,12 +104,12 @@ internal sealed class IOSStorageFile : IOSStorageItem, IStorageBookmarkFile public bool CanOpenWrite => true; - public Task OpenRead() + public Task OpenReadAsync() { return Task.FromResult(new IOSSecurityScopedStream(Url, FileAccess.Read)); } - public Task OpenWrite() + public Task OpenWriteAsync() { return Task.FromResult(new IOSSecurityScopedStream(Url, FileAccess.Write)); } From 002377044aea087f4bf3ca4b2af088be469c7b2b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 7 Jul 2022 09:50:06 +0200 Subject: [PATCH 046/240] Disallow selectors with trailing /template/. It's an invalid selector: what does `Button /template/` select? --- src/Avalonia.Base/Styling/Style.cs | 16 ++++- .../Styling/SelectorTests_Nesting.cs | 72 ------------------- .../Styling/StyleTests.cs | 7 ++ 3 files changed, 22 insertions(+), 73 deletions(-) diff --git a/src/Avalonia.Base/Styling/Style.cs b/src/Avalonia.Base/Styling/Style.cs index 77c4e62d29..c61b08b2a1 100644 --- a/src/Avalonia.Base/Styling/Style.cs +++ b/src/Avalonia.Base/Styling/Style.cs @@ -7,6 +7,8 @@ namespace Avalonia.Styling /// public class Style : StyleBase { + private Selector? _selector; + /// /// Initializes a new instance of the class. /// @@ -26,7 +28,11 @@ namespace Avalonia.Styling /// /// Gets or sets the style's selector. /// - public Selector? Selector { get; set; } + public Selector? Selector + { + get => _selector; + set => _selector = ValidateSelector(value); + } public override SelectorMatchResult TryAttach(IStyleable target, object? host) { @@ -88,5 +94,13 @@ namespace Avalonia.Styling base.SetParent(parent); } + + private static Selector? ValidateSelector(Selector? selector) + { + if (selector is TemplateSelector) + throw new InvalidOperationException( + "Invalid selector: Template selector must be followed by control selector."); + return selector; + } } } diff --git a/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs b/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs index 747ad585e7..9048b488b6 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs @@ -150,78 +150,6 @@ namespace Avalonia.Base.UnitTests.Styling Assert.False(sink.Active); } - [Fact(Skip = "Template selectors a the end of nesting parent selectors currently broken")] - public void Template_Nesting_OfType_Matches() - { - var control = new Control1 { Classes = { "foo" } }; - var button = new Button - { - Template = new FuncControlTemplate((x, _) => control), - }; - - button.ApplyTemplate(); - - Style nested; - var parent = new Style(x => x.OfType IEnumerable Containers { get; } + /// + /// Gets or sets the theme to be applied to the items in the control. + /// + ControlTheme? ItemContainerTheme { get; set; } + /// /// Gets or sets the data template used to display the items in the control. /// diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs index a76dcbe9c8..8b36b07cec 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs @@ -4,6 +4,7 @@ using System.Linq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Styling; namespace Avalonia.Controls.Generators { @@ -35,6 +36,11 @@ namespace Avalonia.Controls.Generators /// public event EventHandler? Recycled; + /// + /// Gets or sets the theme to be applied to the items in the control. + /// + public ControlTheme? ItemContainerTheme { get; set; } + /// /// Gets or sets the data template used to display the items in the control. /// @@ -190,10 +196,18 @@ namespace Avalonia.Controls.Generators result.SetValue( ContentPresenter.ContentTemplateProperty, ItemTemplate, - BindingPriority.TemplatedParent); + BindingPriority.Style); } } + if (ItemContainerTheme != null) + { + result.SetValue( + StyledElement.ThemeProperty, + ItemContainerTheme, + BindingPriority.TemplatedParent); + } + return result; } diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs index 635f3a7d37..3ff1b0702d 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs @@ -44,28 +44,29 @@ namespace Avalonia.Controls.Generators { var container = item as T; - if (container != null) + if (container is null) { - return container; - } - else - { - var result = new T(); + container = new T(); if (ContentTemplateProperty != null) { - result.SetValue(ContentTemplateProperty, ItemTemplate, BindingPriority.Style); + container.SetValue(ContentTemplateProperty, ItemTemplate, BindingPriority.Style); } - result.SetValue(ContentProperty, item, BindingPriority.Style); + container.SetValue(ContentProperty, item, BindingPriority.Style); if (!(item is IControl)) { - result.DataContext = item; + container.DataContext = item; } + } - return result; + if (ItemContainerTheme != null) + { + container.SetValue(StyledElement.ThemeProperty, ItemContainerTheme, BindingPriority.Style); } + + return container; } /// diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs index 536a5fdd06..4e3deb5552 100644 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs @@ -71,6 +71,11 @@ namespace Avalonia.Controls.Generators var template = GetTreeDataTemplate(item, ItemTemplate); var result = new T(); + if (ItemContainerTheme != null) + { + result.SetValue(Control.ThemeProperty, ItemContainerTheme, BindingPriority.Style); + } + result.SetValue(ContentProperty, template.Build(item), BindingPriority.Style); var itemsSelector = template.ItemsSelector(item); diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 56b0014c05..427f5c8a9b 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -15,6 +15,7 @@ using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Metadata; using Avalonia.VisualTree; +using Avalonia.Styling; namespace Avalonia.Controls { @@ -36,6 +37,12 @@ namespace Avalonia.Controls public static readonly DirectProperty ItemsProperty = AvaloniaProperty.RegisterDirect(nameof(Items), o => o.Items, (o, v) => o.Items = v); + /// + /// Defines the property. + /// + public static readonly StyledProperty ItemContainerThemeProperty = + AvaloniaProperty.Register(nameof(ItemContainerTheme)); + /// /// Defines the property. /// @@ -88,6 +95,7 @@ namespace Avalonia.Controls { _itemContainerGenerator = CreateItemContainerGenerator(); + _itemContainerGenerator.ItemContainerTheme = ItemContainerTheme; _itemContainerGenerator.ItemTemplate = ItemTemplate; _itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e); _itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e); @@ -108,6 +116,15 @@ namespace Avalonia.Controls set { SetAndRaise(ItemsProperty, ref _items, value); } } + /// + /// Gets or sets the that is applied to the container element generated for each item. + /// + public ControlTheme? ItemContainerTheme + { + get { return GetValue(ItemContainerThemeProperty); } + set { SetValue(ItemContainerThemeProperty, value); } + } + /// /// Gets the number of items in . /// @@ -349,6 +366,10 @@ namespace Avalonia.Controls { UpdatePseudoClasses(change.GetNewValue()); } + else if (change.Property == ItemContainerThemeProperty && _itemContainerGenerator is not null) + { + _itemContainerGenerator.ItemContainerTheme = change.GetNewValue(); + } } /// diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index 490b0b3ce3..2e3aa037c2 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -121,6 +121,11 @@ namespace Avalonia.Controls { ItemTemplate = _treeView.ItemTemplate; } + + if (ItemContainerTheme == null && _treeView?.ItemContainerTheme != null) + { + ItemContainerTheme = _treeView.ItemContainerTheme; + } } protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index bfece7871c..f08653a4f8 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -1,15 +1,14 @@ +using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using Avalonia.Collections; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; +using Avalonia.Input; using Avalonia.LogicalTree; -using Avalonia.VisualTree; -using Xunit; -using System.Collections.ObjectModel; +using Avalonia.Styling; using Avalonia.UnitTests; -using Avalonia.Input; -using System.Collections.Generic; +using Xunit; namespace Avalonia.Controls.UnitTests { @@ -62,6 +61,25 @@ namespace Avalonia.Controls.UnitTests Assert.Null(container.TemplatedParent); } + [Fact] + public void Container_Should_Have_Theme_Set_To_ItemContainerTheme() + { + var theme = new ControlTheme(); + var target = new ItemsControl + { + ItemContainerTheme = theme, + }; + + target.Template = GetTemplate(); + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + var container = (ContentPresenter)target.Presenter.Panel.Children[0]; + + Assert.Same(container.Theme, theme); + } + [Fact] public void Container_Should_Have_LogicalParent_Set_To_ItemsControl() { diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index e87990ebb1..afa153a593 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -94,6 +94,50 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Container_Should_Have_Theme_Set_To_ItemContainerTheme() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + var items = new[] { "Foo", "Bar", "Baz " }; + var theme = new ControlTheme(); + var target = new ListBox + { + Template = ListBoxTemplate(), + Items = items, + ItemContainerTheme = theme, + }; + + Prepare(target); + + var container = (ListBoxItem)target.Presenter.Panel.Children[0]; + + Assert.Same(container.Theme, theme); + } + } + + [Fact] + public void Inline_Item_Should_Have_Theme_Set_To_ItemContainerTheme() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + var items = new[] { "Foo", "Bar", "Baz " }; + var theme = new ControlTheme(); + var target = new ListBox + { + Template = ListBoxTemplate(), + Items = new[] { new ListBoxItem() }, + ItemContainerTheme = theme, + }; + + Prepare(target); + + var container = (ListBoxItem)target.Presenter.Panel.Children[0]; + + Assert.Same(container.Theme, theme); + } + } + [Fact] public void LogicalChildren_Should_Be_Set_For_DataTemplate_Generated_Items() { diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 2169b15cad..d784caf2db 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -72,6 +72,35 @@ namespace Avalonia.Controls.UnitTests Assert.All(items, x => Assert.IsType(x.HeaderPresenter.Child)); } + [Fact] + public void Items_Should_Be_Created_Using_ItemConatinerTheme_If_Present() + { + TreeView target; + var theme = new ControlTheme(); + + var root = new TestRoot + { + Child = target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = CreateTestTreeData(), + ItemContainerTheme = theme, + ItemTemplate = new FuncTreeDataTemplate( + (_, __) => new Canvas(), + x => x.Children), + } + }; + + ApplyTemplates(target); + + var items = target.ItemContainerGenerator.Index.Containers + .OfType() + .ToList(); + + Assert.Equal(5, items.Count); + Assert.All(items, x => Assert.Same(theme, x.ItemContainerTheme)); + } + [Fact] public void Root_ItemContainerGenerator_Containers_Should_Be_Root_Containers() { From 7cbeb7ad3f4aac3412efd662d5dac4f6f984cdbb Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 8 Jul 2022 18:56:29 +0200 Subject: [PATCH 048/240] Rework TextLineImpl hit testing --- .../ControlCatalog/Pages/TextBlockPage.xaml | 2 +- samples/Sandbox/MainWindow.axaml | 1 + samples/Sandbox/Sandbox.csproj | 1 + src/Avalonia.Base/Media/GlyphRun.cs | 109 ++++- .../Media/TextFormatting/TextLineImpl.cs | 371 +++++++++--------- .../Media/TextFormatting/TextLayoutTests.cs | 84 +++- 6 files changed, 366 insertions(+), 202 deletions(-) diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml index cb49ba96c6..32914428ed 100644 --- a/samples/ControlCatalog/Pages/TextBlockPage.xaml +++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml @@ -118,7 +118,7 @@ - + This is a TextBlock with several diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index 6929f192c7..0c5a7a11e3 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,4 +1,5 @@ + diff --git a/samples/Sandbox/Sandbox.csproj b/samples/Sandbox/Sandbox.csproj index eab654acb6..20c7f29201 100644 --- a/samples/Sandbox/Sandbox.csproj +++ b/samples/Sandbox/Sandbox.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index ac87d521a5..703b56b0e8 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -614,17 +614,29 @@ namespace Avalonia.Media private GlyphRunMetrics CreateGlyphRunMetrics() { + var firstCluster = 0; + var lastCluster = Characters.Length - 1; + + if (!IsLeftToRight) + { + var cluster = firstCluster; + firstCluster = lastCluster; + lastCluster = cluster; + } + if (GlyphClusters != null && GlyphClusters.Count > 0) { - var firstCluster = GlyphClusters[0]; + firstCluster = GlyphClusters[0]; + lastCluster = GlyphClusters[GlyphClusters.Count - 1]; _offsetToFirstCharacter = Math.Max(0, Characters.Start - firstCluster); } + var isReversed = firstCluster > lastCluster; var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; var widthIncludingTrailingWhitespace = 0d; - var trailingWhitespaceLength = GetTrailingWhitespaceLength(out var newLineLength, out var glyphCount); + var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount); for (var index = 0; index < GlyphIndices.Count; index++) { @@ -635,16 +647,16 @@ namespace Avalonia.Media var width = widthIncludingTrailingWhitespace; - if (IsLeftToRight) + if (isReversed) { - for (var index = GlyphIndices.Count - glyphCount; index < GlyphIndices.Count; index++) + for (var index = 0; index < glyphCount; index++) { width -= GetGlyphAdvance(index, out _); - } + } } else { - for (var index = 0; index < glyphCount; index++) + for (var index = GlyphIndices.Count - glyphCount; index < GlyphIndices.Count; index++) { width -= GetGlyphAdvance(index, out _); } @@ -654,16 +666,15 @@ namespace Avalonia.Media height); } - private int GetTrailingWhitespaceLength(out int newLineLength, out int glyphCount) - { - glyphCount = 0; - newLineLength = 0; - - if (Characters.IsEmpty) + private int GetTrailingWhitespaceLength(bool isReversed, out int newLineLength, out int glyphCount) + { + if (isReversed) { - return 0; + return GetTralingWhitespaceLengthRightToLeft(out newLineLength, out glyphCount); } + glyphCount = 0; + newLineLength = 0; var trailingWhitespaceLength = 0; if (GlyphClusters == null) @@ -732,6 +743,78 @@ namespace Avalonia.Media return trailingWhitespaceLength; } + private int GetTralingWhitespaceLengthRightToLeft(out int newLineLength, out int glyphCount) + { + glyphCount = 0; + newLineLength = 0; + var trailingWhitespaceLength = 0; + + if (GlyphClusters == null) + { + for (var i = 0; i < Characters.Length;) + { + var codepoint = Codepoint.ReadAt(_characters, i, out var count); + + if (!codepoint.IsWhiteSpace) + { + break; + } + + if (codepoint.IsBreakChar) + { + newLineLength++; + } + + trailingWhitespaceLength++; + + i += count; + glyphCount++; + } + } + else + { + for (var i = 0; i < GlyphClusters.Count; i++) + { + var currentCluster = GlyphClusters[i]; + var characterIndex = Math.Max(0, currentCluster - _characters.BufferOffset); + var codepoint = Codepoint.ReadAt(_characters, characterIndex, out _); + + if (!codepoint.IsWhiteSpace) + { + break; + } + + var clusterLength = 1; + + while (i - 1 >= 0) + { + var nextCluster = GlyphClusters[i - 1]; + + if (currentCluster == nextCluster) + { + clusterLength++; + i--; + + continue; + } + + break; + } + + if (codepoint.IsBreakChar) + { + newLineLength += clusterLength; + } + + trailingWhitespaceLength += clusterLength; + + glyphCount++; + } + } + + return trailingWhitespaceLength; + } + private void Set(ref T field, T value) { _glyphRunImpl?.Dispose(); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 7c686358e2..f4a0324d90 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -166,58 +166,74 @@ namespace Avalonia.Media.TextFormatting if (distance <= 0) { - // hit happens before the line, return the first position var firstRun = _textRuns[0]; - if (firstRun is ShapedTextCharacters shapedTextCharacters) - { - return shapedTextCharacters.GlyphRun.GetCharacterHitFromDistance(distance, out _); - } + return GetRunCharacterHit(firstRun, FirstTextSourceIndex, 0); + } - return _resolvedFlowDirection == FlowDirection.LeftToRight ? - new CharacterHit(FirstTextSourceIndex) : - new CharacterHit(FirstTextSourceIndex + Length); + if (distance > WidthIncludingTrailingWhitespace) + { + var lastRun = _textRuns[_textRuns.Count - 1]; + + return GetRunCharacterHit(lastRun, FirstTextSourceIndex + Length - lastRun.TextSourceLength, lastRun.Size.Width); } // process hit that happens within the line var characterHit = new CharacterHit(); var currentPosition = FirstTextSourceIndex; + var currentDistance = 0.0; foreach (var currentRun in _textRuns) { - switch (currentRun) + if (currentDistance + currentRun.Size.Width < distance) { - case ShapedTextCharacters shapedRun: - { - characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); + currentDistance += currentRun.Size.Width; + currentPosition += currentRun.TextSourceLength; - var offset = Math.Max(0, currentPosition - shapedRun.Text.Start); + continue; + } - characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); + characterHit = GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance); - break; - } - default: + break; + } + + return characterHit; + } + + private static CharacterHit GetRunCharacterHit(DrawableTextRun run, int currentPosition, double distance) + { + CharacterHit characterHit; + + switch (run) + { + case ShapedTextCharacters shapedRun: + { + characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); + + var offset = Math.Max(0, currentPosition - shapedRun.Text.Start); + + if (!shapedRun.GlyphRun.IsLeftToRight) { - if (distance < currentRun.Size.Width / 2) - { - characterHit = new CharacterHit(currentPosition); - } - else - { - characterHit = new CharacterHit(currentPosition, currentRun.TextSourceLength); - } - break; + offset = Math.Max(0, offset - shapedRun.Text.End); } - } - if (distance <= currentRun.Size.Width) - { - break; - } + characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); - distance -= currentRun.Size.Width; - currentPosition += currentRun.TextSourceLength; + break; + } + default: + { + if (distance < run.Size.Width / 2) + { + characterHit = new CharacterHit(currentPosition); + } + else + { + characterHit = new CharacterHit(currentPosition, run.TextSourceLength); + } + break; + } } return characterHit; @@ -226,136 +242,122 @@ namespace Avalonia.Media.TextFormatting /// public override double GetDistanceFromCharacterHit(CharacterHit characterHit) { - var isTrailingHit = characterHit.TrailingLength > 0; + var flowDirection = _paragraphProperties.FlowDirection; var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; - var currentDistance = Start; var currentPosition = FirstTextSourceIndex; var remainingLength = characterIndex - FirstTextSourceIndex; - GlyphRun? lastRun = null; + var currentDistance = Start; - for (var index = 0; index < _textRuns.Count; index++) + if (flowDirection == FlowDirection.LeftToRight) + { + for (var index = 0; index < _textRuns.Count; index++) + { + var currentRun = _textRuns[index]; + + if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, + flowDirection, out var distance, out _)) + { + return currentDistance + distance; + } + + //No hit hit found so we add the full width + currentDistance += currentRun.Size.Width; + currentPosition += currentRun.TextSourceLength; + remainingLength -= currentRun.TextSourceLength; + } + } + else { - var textRun = _textRuns[index]; + currentDistance += WidthIncludingTrailingWhitespace; - switch (textRun) + for (var index = _textRuns.Count - 1; index >= 0; index--) { - case ShapedTextCharacters shapedTextCharacters: + var currentRun = _textRuns[index]; + + if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, + flowDirection, out var distance, out var currentGlyphRun)) + { + if (currentGlyphRun != null) { - var currentRun = shapedTextCharacters.GlyphRun; + distance = currentGlyphRun.Size.Width - distance; + } - if (lastRun != null) - { - if (!lastRun.IsLeftToRight && currentRun.IsLeftToRight && - currentRun.Characters.Start == characterHit.FirstCharacterIndex && - characterHit.TrailingLength == 0) - { - return currentDistance; - } - } + return currentDistance - distance; + } - //Look for a hit in within the current run - if (currentPosition + remainingLength <= currentPosition + textRun.Text.Length) - { - characterHit = new CharacterHit(textRun.Text.Start + remainingLength); + //No hit hit found so we add the full width + currentDistance -= currentRun.Size.Width; + currentPosition += currentRun.TextSourceLength; + remainingLength -= currentRun.TextSourceLength; + } + } - var distance = currentRun.GetDistanceFromCharacterHit(characterHit); + return currentDistance; + } - return currentDistance + distance; - } + private static bool TryGetDistanceFromCharacterHit( + DrawableTextRun currentRun, + CharacterHit characterHit, + int currentPosition, + int remainingLength, + FlowDirection flowDirection, + out double distance, + out GlyphRun? currentGlyphRun) + { + var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; + var isTrailingHit = characterHit.TrailingLength > 0; - //Look at the left and right edge of the current run - if (currentRun.IsLeftToRight) - { - if (_resolvedFlowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight)) - { - if (characterIndex <= currentPosition) - { - return currentDistance; - } - } - else - { - if (characterIndex == currentPosition) - { - return currentDistance; - } - } + distance = 0; + currentGlyphRun = null; - if (characterIndex == currentPosition + textRun.Text.Length && isTrailingHit) - { - return currentDistance + currentRun.Size.Width; - } - } - else - { - if (characterIndex == currentPosition) - { - return currentDistance + currentRun.Size.Width; - } - - var nextRun = index + 1 < _textRuns.Count ? - _textRuns[index + 1] as ShapedTextCharacters : - null; + switch (currentRun) + { + case ShapedTextCharacters shapedTextCharacters: + { + currentGlyphRun = shapedTextCharacters.GlyphRun; - if (nextRun != null) - { - if (nextRun.ShapedBuffer.IsLeftToRight) - { - if (characterIndex == currentPosition + textRun.Text.Length) - { - return currentDistance; - } - } - else - { - if (currentPosition + nextRun.Text.Length == characterIndex) - { - return currentDistance; - } - } - } - else - { - if (characterIndex > currentPosition + textRun.Text.Length) - { - return currentDistance; - } - } - } + if (currentPosition + remainingLength < currentPosition + currentRun.Text.Length) + { + characterHit = new CharacterHit(currentRun.Text.Start + remainingLength); - lastRun = currentRun; + distance = currentGlyphRun.GetDistanceFromCharacterHit(characterHit); - break; + return true; } - default: + + if (currentPosition + remainingLength == currentPosition + currentRun.Text.Length && isTrailingHit) { - if (characterIndex == currentPosition) + if (currentGlyphRun.IsLeftToRight || flowDirection == FlowDirection.RightToLeft) { - return currentDistance; + distance = currentGlyphRun.Size.Width; } - if (characterIndex == currentPosition + textRun.TextSourceLength) - { - return currentDistance + textRun.Size.Width; - } + return true; + } - break; + break; + } + default: + { + if (characterIndex == currentPosition) + { + return true; } - } - //No hit hit found so we add the full width - currentDistance += textRun.Size.Width; - currentPosition += textRun.TextSourceLength; - remainingLength -= textRun.TextSourceLength; + if (characterIndex == currentPosition + currentRun.TextSourceLength) + { + distance = currentRun.Size.Width; - if (remainingLength <= 0) - { - break; - } + return true; + + } + + break; + } } - return currentDistance; + return false; } /// @@ -460,20 +462,33 @@ namespace Avalonia.Media.TextFormatting var startIndex = currentRun.Text.Start + offset; - var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( - currentShapedRun.ShapedBuffer.IsLeftToRight ? - new CharacterHit(startIndex + remainingLength) : - new CharacterHit(startIndex)); + double startOffset; + double endOffset; - endX += endOffset; + if (currentShapedRun.ShapedBuffer.IsLeftToRight) + { + startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); + + endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + } + else + { + endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); - var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( - currentShapedRun.ShapedBuffer.IsLeftToRight ? - new CharacterHit(startIndex) : - new CharacterHit(startIndex + remainingLength)); + if (currentPosition < startIndex) + { + startOffset = endOffset; + } + else + { + startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + } + } startX += startOffset; + endX += endOffset; + var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); @@ -504,7 +519,7 @@ namespace Avalonia.Media.TextFormatting } //Lines that only contain a linebreak need to be covered here - if(characterLength == 0) + if (characterLength == 0) { characterLength = NewLineLength; } @@ -532,19 +547,9 @@ namespace Avalonia.Media.TextFormatting currentWidth += runwidth; currentPosition += characterLength; - if (currentDirection == FlowDirection.LeftToRight) + if (currentPosition > characterIndex) { - if (currentPosition > characterIndex) - { - break; - } - } - else - { - if (currentPosition <= firstTextSourceIndex) - { - break; - } + break; } startX = endX; @@ -571,7 +576,7 @@ namespace Avalonia.Media.TextFormatting var currentPosition = FirstTextSourceIndex; var remainingLength = textLength; - var startX = Start + WidthIncludingTrailingWhitespace; + var startX = WidthIncludingTrailingWhitespace; double currentWidth = 0; var currentRect = Rect.Empty; @@ -582,7 +587,7 @@ namespace Avalonia.Media.TextFormatting continue; } - if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex) + if (currentPosition + currentRun.TextSourceLength < firstTextSourceIndex) { startX -= currentRun.Size.Width; @@ -601,20 +606,31 @@ namespace Avalonia.Media.TextFormatting currentPosition += offset; var startIndex = currentRun.Text.Start + offset; + double startOffset; + double endOffset; - var endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( - currentShapedRun.ShapedBuffer.IsLeftToRight ? - new CharacterHit(startIndex + remainingLength) : - new CharacterHit(startIndex)); + if (currentShapedRun.ShapedBuffer.IsLeftToRight) + { + if (currentPosition < startIndex) + { + startOffset = endOffset = 0; + } + else + { + endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); - endX += endOffset - currentShapedRun.Size.Width; + startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); + } + } + else + { + endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); - var startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit( - currentShapedRun.ShapedBuffer.IsLeftToRight ? - new CharacterHit(startIndex) : - new CharacterHit(startIndex + remainingLength)); + startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + } - startX += startOffset - currentShapedRun.Size.Width; + startX -= currentRun.Size.Width - startOffset; + endX -= currentRun.Size.Width - endOffset; var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); @@ -652,9 +668,10 @@ namespace Avalonia.Media.TextFormatting } var runWidth = endX - startX; - var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); - if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX)) + var currentRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); + + if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, Start + startX)) { currentRect = currentRect.WithWidth(currentWidth + runWidth); @@ -674,19 +691,9 @@ namespace Avalonia.Media.TextFormatting currentWidth += runWidth; currentPosition += characterLength; - if (currentDirection == FlowDirection.LeftToRight) + if (currentPosition > characterIndex) { - if (currentPosition > characterIndex) - { - break; - } - } - else - { - if (currentPosition <= firstTextSourceIndex) - { - break; - } + break; } lastDirection = currentDirection; diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 7e1103d624..631d0881b0 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -154,7 +154,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { j += inner.Current.Text.Length; - if(j + i > text.Length) + if (j + i > text.Length) { break; } @@ -738,7 +738,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = layout.TextLines[0]; var start = textLine.GetDistanceFromCharacterHit(new CharacterHit(5, 1)); - + var end = textLine.GetDistanceFromCharacterHit(new CharacterHit(6, 1)); var rects = layout.HitTestTextRange(0, 7).ToArray(); @@ -746,7 +746,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(1, rects.Length); var expected = rects[0]; - + Assert.Equal(expected.Left, start); Assert.Equal(expected.Right, end); } @@ -818,11 +818,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var expected = text.Substring(textLine.FirstTextSourceIndex, textLine.Length); Assert.Equal(expected, actual); - } + } } } } - + [Fact] public void Should_Layout_Empty_String() { @@ -833,11 +833,83 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Typeface.Default, 12, Brushes.Black); - + Assert.True(layout.Bounds.Height > 0); } } + [Fact] + public void Should_HitTestPoint_RightToLeft() + { + using (Start()) + { + var text = "אאא AAA"; + + var layout = new TextLayout( + text, + Typeface.Default, + 12, + Brushes.Black, + flowDirection: FlowDirection.RightToLeft); + + var firstRun = layout.TextLines[0].TextRuns[0] as ShapedTextCharacters; + + var hit = layout.HitTestPoint(new Point()); + + Assert.Equal(4, hit.TextPosition); + + var currentX = 0.0; + + for (var i = 0; i < firstRun.GlyphRun.GlyphClusters.Count; i++) + { + var cluster = firstRun.GlyphRun.GlyphClusters[i]; + var advance = firstRun.GlyphRun.GlyphAdvances[i]; + + hit = layout.HitTestPoint(new Point(currentX, 0)); + + Assert.Equal(cluster, hit.TextPosition); + + var hitRange = layout.HitTestTextRange(hit.TextPosition, 1); + + var distance = hitRange.First().Left; + + Assert.Equal(currentX, distance); + + currentX += advance; + } + + var secondRun = layout.TextLines[0].TextRuns[1] as ShapedTextCharacters; + + hit = layout.HitTestPoint(new Point(firstRun.Size.Width, 0)); + + Assert.Equal(7, hit.TextPosition); + + hit = layout.HitTestPoint(new Point(layout.TextLines[0].WidthIncludingTrailingWhitespace, 0)); + + Assert.Equal(0, hit.TextPosition); + + currentX = firstRun.Size.Width + 0.5; + + for (var i = 0; i < secondRun.GlyphRun.GlyphClusters.Count; i++) + { + var cluster = secondRun.GlyphRun.GlyphClusters[i]; + var advance = secondRun.GlyphRun.GlyphAdvances[i]; + + hit = layout.HitTestPoint(new Point(currentX, 0)); + + Assert.Equal(cluster, hit.CharacterHit.FirstCharacterIndex); + + var hitRange = layout.HitTestTextRange(hit.CharacterHit.FirstCharacterIndex, hit.CharacterHit.TrailingLength); + + var distance = hitRange.First().Left + 0.5; + + Assert.Equal(currentX, distance); + + currentX += advance; + } + } + } + private static IDisposable Start() { var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface From 2b3608078ea33439364d5ff61cf1868e2026b3bf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 7 Jul 2022 11:03:55 +0200 Subject: [PATCH 049/240] WIP: Porting fluent theme to use control themes. Co-Authored-By: Takoooooo --- .../DateTimePickers/DateTimePickerPanel.cs | 2 +- .../Controls/AutoCompleteBox.xaml | 110 ++-- .../Controls/Button.xaml | 41 +- .../Controls/ButtonSpinner.xaml | 229 +++---- .../Controls/Calendar.xaml | 56 +- .../Controls/CalendarButton.xaml | 204 +++--- .../Controls/CalendarDatePicker.xaml | 346 +++++----- .../Controls/CalendarDayButton.xaml | 194 +++--- .../Controls/CalendarItem.xaml | 275 ++++---- .../Controls/CaptionButtons.xaml | 129 ++-- .../Controls/Carousel.xaml | 37 +- .../Controls/CheckBox.xaml | 572 ++++++++-------- .../Controls/ComboBox.xaml | 440 ++++++------- .../Controls/ComboBoxItem.xaml | 171 +++-- .../Controls/Common.xaml | 6 - .../Controls/ContentControl.xaml | 35 +- .../Controls/ContextMenu.xaml | 136 ++-- .../Controls/DataValidationErrors.xaml | 207 +++--- .../Controls/DatePicker.xaml | 622 +++++++++--------- .../Controls/DropDownButton.xaml | 198 +++--- .../Controls/EmbeddableControlRoot.xaml | 42 +- .../Controls/Expander.xaml | 354 +++++----- .../Controls/FluentControls.xaml | 146 ++-- .../Controls/FlyoutPresenter.xaml | 14 +- .../Controls/FocusAdorner.xaml | 2 +- .../Controls/GridSplitter.xaml | 46 +- .../Controls/ItemsControl.xaml | 35 +- .../Controls/Label.xaml | 40 +- .../Controls/ListBox.xaml | 92 +-- .../Controls/ListBoxItem.xaml | 155 ++--- .../Controls/ManagedFileChooser.xaml | 608 ++++++++--------- src/Avalonia.Themes.Fluent/Controls/Menu.xaml | 65 +- .../Controls/MenuFlyoutPresenter.xaml | 69 +- .../Controls/MenuItem.xaml | 4 +- .../Controls/NativeMenuBar.xaml | 59 +- .../Controls/NotificationCard.xaml | 37 +- .../Controls/NumericUpDown.xaml | 135 ++-- .../Controls/OverlayPopupHost.xaml | 43 +- .../Controls/PathIcon.xaml | 55 +- .../Controls/PopupRoot.xaml | 56 +- .../Controls/ProgressBar.xaml | 343 +++++----- .../Controls/RadioButton.xaml | 286 ++++---- .../Controls/RepeatButton.xaml | 118 ++-- .../Controls/ScrollBar.xaml | 2 +- .../Controls/ScrollViewer.xaml | 245 +++---- .../Controls/Separator.xaml | 15 +- .../Controls/Slider.xaml | 603 ++++++++++------- .../Controls/SplitButton.xaml | 1 - .../Controls/SplitView.xaml | 2 +- .../Controls/TabControl.xaml | 122 ++-- .../Controls/TabItem.xaml | 237 +++---- .../Controls/TabStrip.xaml | 66 +- .../Controls/TabStripItem.xaml | 193 +++--- .../Controls/TextBox.xaml | 62 +- .../Controls/TimePicker.xaml | 409 ++++-------- .../Controls/TitleBar.xaml | 94 +-- .../Controls/ToggleButton.xaml | 189 +++--- .../Controls/ToggleSwitch.xaml | 585 ++++++++-------- .../Controls/ToolTip.xaml | 146 ++-- .../Controls/TransitioningContentControl.xaml | 39 +- .../Controls/TreeView.xaml | 59 +- .../Controls/TreeViewItem.xaml | 3 +- .../Controls/UserControl.xaml | 38 +- .../Controls/Window.xaml | 57 +- .../Controls/WindowNotificationManager.xaml | 97 +-- 65 files changed, 4976 insertions(+), 5102 deletions(-) delete mode 100644 src/Avalonia.Themes.Fluent/Controls/Common.xaml diff --git a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs index 667f994a1d..3fdfbee54d 100644 --- a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs +++ b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs @@ -454,7 +454,7 @@ namespace Avalonia.Controls.Primitives children.Add(new ListBoxItem { Height = ItemHeight, - Classes = new Classes("DateTimePickerItem", $"{PanelType}Item"), + Classes = new Classes($"{PanelType}Item"), VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Center, Focusable = false }); diff --git a/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml b/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml index 58295d024f..6004d42120 100644 --- a/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml @@ -1,5 +1,5 @@ - + @@ -16,56 +16,56 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/Button.xaml b/src/Avalonia.Themes.Fluent/Controls/Button.xaml index 3fb0d43342..b8fea84ae0 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Button.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Button.xaml @@ -1,4 +1,5 @@ - + @@ -7,14 +8,11 @@ - - - 8,5,8,6 - - - - - - - - + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml index 836cc27db3..a5234f5771 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml @@ -1,28 +1,26 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + - @@ -32,118 +30,79 @@ M0,9 L10,0 20,9 19,10 10,2 1,10 z M0,1 L10,10 20,1 19,0 10,8 1,0 z - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml b/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml index 43e4a1fcea..3434f9b6e5 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml @@ -1,38 +1,40 @@ - - - + + - - + CornerRadius="{TemplateBinding CornerRadius}" + HeaderBackground="{TemplateBinding HeaderBackground}" /> + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml index 3a6af60983..0381022910 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml @@ -1,121 +1,109 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + + + + - + - - + + + - - - - - - + + + + + - - + + + + - - - - - - - + - - - - - - - - - - - - + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml index c6f70d05e1..83a254be56 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml @@ -1,182 +1,180 @@ - - - - - - - - + + + + + + + 12 32 - - - - - - - - + Width="3" + Height="3" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml b/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml index 62874f4884..3c45de18c6 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml @@ -1,72 +1,57 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/Carousel.xaml b/src/Avalonia.Themes.Fluent/Controls/Carousel.xaml index baba0649aa..6c05a62250 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Carousel.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Carousel.xaml @@ -1,17 +1,20 @@ - + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml index e9830fb228..2eee425cb0 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml @@ -1,297 +1,293 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - + + + + + + - + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml index 93ecc438eb..2dc7439001 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml @@ -1,232 +1,228 @@ - - - - - - Item 1 - Item 2 - - - Item 1 - Item 2 - - - - - + + + + + + Item 1 + Item 2 + + + Item 1 + Item 2 + + + + + 0,0,0,4 15 7 - + 12,5,0,7 11,5,32,6 32 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Background="Transparent" + Margin="0,1,1,1" + Width="30" + IsVisible="False" + HorizontalAlignment="Right" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + diff --git a/src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml b/src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml index 0debe87445..88f9c0ea15 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml @@ -1,90 +1,85 @@ - - - - - - Item 1 - Item 2 long - Item 3 - Item 4 - + + + + + + Item 1 + Item 2 long + Item 3 + Item 4 + + - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/Common.xaml b/src/Avalonia.Themes.Fluent/Controls/Common.xaml deleted file mode 100644 index e09e39d7cb..0000000000 --- a/src/Avalonia.Themes.Fluent/Controls/Common.xaml +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/src/Avalonia.Themes.Fluent/Controls/ContentControl.xaml b/src/Avalonia.Themes.Fluent/Controls/ContentControl.xaml index d32bc399b6..5777603074 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ContentControl.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ContentControl.xaml @@ -1,16 +1,19 @@ - + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml b/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml index df800b4a06..fe0f0f3f6c 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml @@ -1,72 +1,72 @@ - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml b/src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml index 649a186c7e..6ed73148a7 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml @@ -1,104 +1,103 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml index 11d6b9fdfc..a7093c1341 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml @@ -5,17 +5,16 @@ // All other rights reserved. --> - - - - - - - - + + + + + + 0,0,0,4 40 40 @@ -27,328 +26,327 @@ 0,3,0,6 9,3,0,6 1 - - - - - - - - + + + + + + + + + + + - - + + + + + - + - + + + + + + + + + + + + - - + + + + + + + + + + - - - + + + + - + - + + + - + - - - - - + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + - - - - + diff --git a/src/Avalonia.Themes.Fluent/Controls/DropDownButton.xaml b/src/Avalonia.Themes.Fluent/Controls/DropDownButton.xaml index b96c689ab6..3206cb8cb1 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DropDownButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DropDownButton.xaml @@ -1,103 +1,101 @@ - - - - - - - - - - - - - 32 - + + + + + + + + + - - - - - - - - - - + 32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml index 79560be933..89eaec70ad 100644 --- a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml @@ -1,19 +1,23 @@ - + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml index 33d502772e..36f6008782 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml @@ -1,6 +1,6 @@ - + @@ -34,72 +34,34 @@ - - 16 - 16 + 16 + 16 - 1 + 1 - 1,1,0,1 - 1,1,1,0 - 0,1,1,1 - 1,0,1,1 + 1,1,0,1 + 1,1,1,0 + 0,1,1,1 + 1,0,1,1 - - + + - - + + - + - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 5b217e4764..286b3e3fcd 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -1,70 +1,78 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml b/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml index 92f8177ead..2dca5b0770 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml @@ -1,9 +1,7 @@ - - - 1 - - - - + + diff --git a/src/Avalonia.Themes.Fluent/Controls/FocusAdorner.xaml b/src/Avalonia.Themes.Fluent/Controls/FocusAdorner.xaml index 91bf71ed4d..c3f489da80 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FocusAdorner.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FocusAdorner.xaml @@ -1,4 +1,4 @@ - 0 diff --git a/src/Avalonia.Themes.Fluent/Controls/GridSplitter.xaml b/src/Avalonia.Themes.Fluent/Controls/GridSplitter.xaml index e3a7b04f33..ca57eccd13 100644 --- a/src/Avalonia.Themes.Fluent/Controls/GridSplitter.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/GridSplitter.xaml @@ -1,24 +1,22 @@ - - - - - + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ItemsControl.xaml b/src/Avalonia.Themes.Fluent/Controls/ItemsControl.xaml index 19d13b6399..b8d7c2c4ef 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ItemsControl.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ItemsControl.xaml @@ -1,16 +1,19 @@ - + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/Label.xaml b/src/Avalonia.Themes.Fluent/Controls/Label.xaml index d41e4e2166..ad57239648 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Label.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Label.xaml @@ -1,18 +1,22 @@ - + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml index 8011ed9daf..b1d4b77a3f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml @@ -1,45 +1,47 @@ - - - - - Test - Test - Test - Test - - - - - + + + + + Test + Test + Test + Test + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml b/src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml index 11f3c12772..c00e920f75 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml @@ -1,94 +1,79 @@ - - - - - Disabled - - Test - - Test - - - - + + + + + Disabled + + Test + + Test + + + 12,9,12,12 - - + + + + + + + + - + - - + + - - - + + - - - + + - + - - - + + - - - + + - - - - + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml b/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml index 60e16fcff2..dc454e4fdb 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml @@ -1,324 +1,324 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - - - + + - - + + + + - - + + diff --git a/src/Avalonia.Themes.Fluent/Controls/Menu.xaml b/src/Avalonia.Themes.Fluent/Controls/Menu.xaml index 4eb724a926..d42e413f00 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Menu.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Menu.xaml @@ -1,36 +1,35 @@ - + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/MenuFlyoutPresenter.xaml b/src/Avalonia.Themes.Fluent/Controls/MenuFlyoutPresenter.xaml index ff50acab5e..7769f0a5c9 100644 --- a/src/Avalonia.Themes.Fluent/Controls/MenuFlyoutPresenter.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/MenuFlyoutPresenter.xaml @@ -1,35 +1,34 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml index 33cf6bfdde..b7a9435749 100644 --- a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml @@ -1,4 +1,4 @@ - - -4 + -4 0,0,12,0 24,0,0,0 M 1,0 10,10 l -9,10 -1,-1 L 8,10 -0,1 Z diff --git a/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml b/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml index d40ba0cc1d..753c03992a 100644 --- a/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml @@ -1,30 +1,29 @@ - - - - - - + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml b/src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml index 924d977eb5..e55fcb90e8 100644 --- a/src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml @@ -1,11 +1,11 @@ - + - + - - - - - - - - - - + + diff --git a/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml b/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml index 36ab07e3e3..74ffff0f5c 100644 --- a/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml @@ -1,68 +1,67 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml b/src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml index 31b43c39cd..0b587e6037 100644 --- a/src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml @@ -1,20 +1,23 @@ - + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml b/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml index d4952b3571..966c6c2632 100644 --- a/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml @@ -1,29 +1,26 @@ - - - - - M14 9.50006C11.5147 9.50006 9.5 11.5148 9.5 14.0001C9.5 16.4853 11.5147 18.5001 14 18.5001C15.3488 18.5001 16.559 17.9066 17.3838 16.9666C18.0787 16.1746 18.5 15.1365 18.5 14.0001C18.5 13.5401 18.431 13.0963 18.3028 12.6784C17.7382 10.8381 16.0253 9.50006 14 9.50006ZM11 14.0001C11 12.3432 12.3431 11.0001 14 11.0001C15.6569 11.0001 17 12.3432 17 14.0001C17 15.6569 15.6569 17.0001 14 17.0001C12.3431 17.0001 11 15.6569 11 14.0001Z M21.7093 22.3948L19.9818 21.6364C19.4876 21.4197 18.9071 21.4515 18.44 21.7219C17.9729 21.9924 17.675 22.4693 17.6157 23.0066L17.408 24.8855C17.3651 25.273 17.084 25.5917 16.7055 25.682C14.9263 26.1061 13.0725 26.1061 11.2933 25.682C10.9148 25.5917 10.6336 25.273 10.5908 24.8855L10.3834 23.0093C10.3225 22.4731 10.0112 21.9976 9.54452 21.7281C9.07783 21.4586 8.51117 21.4269 8.01859 21.6424L6.29071 22.4009C5.93281 22.558 5.51493 22.4718 5.24806 22.1859C4.00474 20.8536 3.07924 19.2561 2.54122 17.5137C2.42533 17.1384 2.55922 16.7307 2.8749 16.4977L4.40219 15.3703C4.83721 15.0501 5.09414 14.5415 5.09414 14.0007C5.09414 13.4598 4.83721 12.9512 4.40162 12.6306L2.87529 11.5051C2.55914 11.272 2.42513 10.8638 2.54142 10.4882C3.08038 8.74734 4.00637 7.15163 5.24971 5.82114C5.51684 5.53528 5.93492 5.44941 6.29276 5.60691L8.01296 6.36404C8.50793 6.58168 9.07696 6.54881 9.54617 6.27415C10.0133 6.00264 10.3244 5.52527 10.3844 4.98794L10.5933 3.11017C10.637 2.71803 10.9245 2.39704 11.3089 2.31138C12.19 2.11504 13.0891 2.01071 14.0131 2.00006C14.9147 2.01047 15.8128 2.11485 16.6928 2.31149C17.077 2.39734 17.3643 2.71823 17.4079 3.11017L17.617 4.98937C17.7116 5.85221 18.4387 6.50572 19.3055 6.50663C19.5385 6.507 19.769 6.45838 19.9843 6.36294L21.7048 5.60568C22.0626 5.44818 22.4807 5.53405 22.7478 5.81991C23.9912 7.1504 24.9172 8.74611 25.4561 10.487C25.5723 10.8623 25.4386 11.2703 25.1228 11.5035L23.5978 12.6297C23.1628 12.95 22.9 13.4586 22.9 13.9994C22.9 14.5403 23.1628 15.0489 23.5988 15.3698L25.1251 16.4965C25.441 16.7296 25.5748 17.1376 25.4586 17.5131C24.9198 19.2536 23.9944 20.8492 22.7517 22.1799C22.4849 22.4657 22.0671 22.5518 21.7093 22.3948ZM16.263 22.1966C16.4982 21.4685 16.9889 20.8288 17.6884 20.4238C18.5702 19.9132 19.6536 19.8547 20.5841 20.2627L21.9281 20.8526C22.791 19.8538 23.4593 18.7013 23.8981 17.4552L22.7095 16.5778L22.7086 16.5771C21.898 15.98 21.4 15.0277 21.4 13.9994C21.4 12.9719 21.8974 12.0195 22.7073 11.4227L22.7085 11.4218L23.8957 10.545C23.4567 9.2988 22.7881 8.14636 21.9248 7.1477L20.5922 7.73425L20.5899 7.73527C20.1844 7.91463 19.7472 8.00722 19.3039 8.00663C17.6715 8.00453 16.3046 6.77431 16.1261 5.15465L16.1259 5.15291L15.9635 3.69304C15.3202 3.57328 14.6677 3.50872 14.013 3.50017C13.3389 3.50891 12.6821 3.57367 12.0377 3.69328L11.8751 5.15452C11.7625 6.16272 11.1793 7.05909 10.3019 7.56986C9.41937 8.0856 8.34453 8.14844 7.40869 7.73694L6.07273 7.14893C5.20949 8.14751 4.54092 9.29983 4.10196 10.5459L5.29181 11.4233C6.11115 12.0269 6.59414 12.9837 6.59414 14.0007C6.59414 15.0173 6.11142 15.9742 5.29237 16.5776L4.10161 17.4566C4.54002 18.7044 5.2085 19.8585 6.07205 20.8587L7.41742 20.2682C8.34745 19.8613 9.41573 19.9215 10.2947 20.4292C11.174 20.937 11.7593 21.832 11.8738 22.84L11.8744 22.8445L12.0362 24.3088C13.3326 24.5638 14.6662 24.5638 15.9626 24.3088L16.1247 22.8418C16.1491 22.6217 16.1955 22.4055 16.263 22.1966Z - - - - - - + + + + + M14 9.50006C11.5147 9.50006 9.5 11.5148 9.5 14.0001C9.5 16.4853 11.5147 18.5001 14 18.5001C15.3488 18.5001 16.559 17.9066 17.3838 16.9666C18.0787 16.1746 18.5 15.1365 18.5 14.0001C18.5 13.5401 18.431 13.0963 18.3028 12.6784C17.7382 10.8381 16.0253 9.50006 14 9.50006ZM11 14.0001C11 12.3432 12.3431 11.0001 14 11.0001C15.6569 11.0001 17 12.3432 17 14.0001C17 15.6569 15.6569 17.0001 14 17.0001C12.3431 17.0001 11 15.6569 11 14.0001Z M21.7093 22.3948L19.9818 21.6364C19.4876 21.4197 18.9071 21.4515 18.44 21.7219C17.9729 21.9924 17.675 22.4693 17.6157 23.0066L17.408 24.8855C17.3651 25.273 17.084 25.5917 16.7055 25.682C14.9263 26.1061 13.0725 26.1061 11.2933 25.682C10.9148 25.5917 10.6336 25.273 10.5908 24.8855L10.3834 23.0093C10.3225 22.4731 10.0112 21.9976 9.54452 21.7281C9.07783 21.4586 8.51117 21.4269 8.01859 21.6424L6.29071 22.4009C5.93281 22.558 5.51493 22.4718 5.24806 22.1859C4.00474 20.8536 3.07924 19.2561 2.54122 17.5137C2.42533 17.1384 2.55922 16.7307 2.8749 16.4977L4.40219 15.3703C4.83721 15.0501 5.09414 14.5415 5.09414 14.0007C5.09414 13.4598 4.83721 12.9512 4.40162 12.6306L2.87529 11.5051C2.55914 11.272 2.42513 10.8638 2.54142 10.4882C3.08038 8.74734 4.00637 7.15163 5.24971 5.82114C5.51684 5.53528 5.93492 5.44941 6.29276 5.60691L8.01296 6.36404C8.50793 6.58168 9.07696 6.54881 9.54617 6.27415C10.0133 6.00264 10.3244 5.52527 10.3844 4.98794L10.5933 3.11017C10.637 2.71803 10.9245 2.39704 11.3089 2.31138C12.19 2.11504 13.0891 2.01071 14.0131 2.00006C14.9147 2.01047 15.8128 2.11485 16.6928 2.31149C17.077 2.39734 17.3643 2.71823 17.4079 3.11017L17.617 4.98937C17.7116 5.85221 18.4387 6.50572 19.3055 6.50663C19.5385 6.507 19.769 6.45838 19.9843 6.36294L21.7048 5.60568C22.0626 5.44818 22.4807 5.53405 22.7478 5.81991C23.9912 7.1504 24.9172 8.74611 25.4561 10.487C25.5723 10.8623 25.4386 11.2703 25.1228 11.5035L23.5978 12.6297C23.1628 12.95 22.9 13.4586 22.9 13.9994C22.9 14.5403 23.1628 15.0489 23.5988 15.3698L25.1251 16.4965C25.441 16.7296 25.5748 17.1376 25.4586 17.5131C24.9198 19.2536 23.9944 20.8492 22.7517 22.1799C22.4849 22.4657 22.0671 22.5518 21.7093 22.3948ZM16.263 22.1966C16.4982 21.4685 16.9889 20.8288 17.6884 20.4238C18.5702 19.9132 19.6536 19.8547 20.5841 20.2627L21.9281 20.8526C22.791 19.8538 23.4593 18.7013 23.8981 17.4552L22.7095 16.5778L22.7086 16.5771C21.898 15.98 21.4 15.0277 21.4 13.9994C21.4 12.9719 21.8974 12.0195 22.7073 11.4227L22.7085 11.4218L23.8957 10.545C23.4567 9.2988 22.7881 8.14636 21.9248 7.1477L20.5922 7.73425L20.5899 7.73527C20.1844 7.91463 19.7472 8.00722 19.3039 8.00663C17.6715 8.00453 16.3046 6.77431 16.1261 5.15465L16.1259 5.15291L15.9635 3.69304C15.3202 3.57328 14.6677 3.50872 14.013 3.50017C13.3389 3.50891 12.6821 3.57367 12.0377 3.69328L11.8751 5.15452C11.7625 6.16272 11.1793 7.05909 10.3019 7.56986C9.41937 8.0856 8.34453 8.14844 7.40869 7.73694L6.07273 7.14893C5.20949 8.14751 4.54092 9.29983 4.10196 10.5459L5.29181 11.4233C6.11115 12.0269 6.59414 12.9837 6.59414 14.0007C6.59414 15.0173 6.11142 15.9742 5.29237 16.5776L4.10161 17.4566C4.54002 18.7044 5.2085 19.8585 6.07205 20.8587L7.41742 20.2682C8.34745 19.8613 9.41573 19.9215 10.2947 20.4292C11.174 20.937 11.7593 21.832 11.8738 22.84L11.8744 22.8445L12.0362 24.3088C13.3326 24.5638 14.6662 24.5638 15.9626 24.3088L16.1247 22.8418C16.1491 22.6217 16.1955 22.4055 16.263 22.1966Z + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml index f608cf55f5..f57ecd2013 100644 --- a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml @@ -1,28 +1,28 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml index a463334a76..3c7d22e13f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml @@ -1,164 +1,179 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml b/src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml index e967dc8807..a1c4ba4f2d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml @@ -1,4 +1,4 @@ - + @@ -9,7 +9,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml b/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml index 7fa515d3f7..670212c4b1 100644 --- a/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml @@ -1,63 +1,65 @@ - - - - - - - - - + + + + + + + + + 8,5,8,6 - - - + + + + + + + + + + + + + + + + - + - + - - + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml index 4727ff72b9..7d87a3e35a 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml @@ -1,4 +1,4 @@ - + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/Separator.xaml b/src/Avalonia.Themes.Fluent/Controls/Separator.xaml index 5d95ccc404..afbc3770c1 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Separator.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Separator.xaml @@ -1,7 +1,7 @@ - - - - - + + diff --git a/src/Avalonia.Themes.Fluent/Controls/Slider.xaml b/src/Avalonia.Themes.Fluent/Controls/Slider.xaml index cd2c02c567..b468f7f4ad 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Slider.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Slider.xaml @@ -1,19 +1,20 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + 0,0,0,4 15 15 @@ -24,252 +25,364 @@ 20 20 20 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + - + - + + + + - + - - - - - - - - - + - - - - - - - - - - - + - - - - - - - - + - - + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml b/src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml index 91c901f567..fb0460d9a1 100644 --- a/src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml @@ -2,7 +2,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:CompileBindings="True" xmlns:converters="using:Avalonia.Controls.Converters"> - diff --git a/src/Avalonia.Themes.Fluent/Controls/SplitView.xaml b/src/Avalonia.Themes.Fluent/Controls/SplitView.xaml index 55d46e32a1..6b9b94852f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/SplitView.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/SplitView.xaml @@ -1,4 +1,4 @@ - diff --git a/src/Avalonia.Themes.Fluent/Controls/TabControl.xaml b/src/Avalonia.Themes.Fluent/Controls/TabControl.xaml index 322f6ce89e..1cc31f8e15 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TabControl.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TabControl.xaml @@ -1,65 +1,63 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - 0 0 0 2 - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/TabItem.xaml b/src/Avalonia.Themes.Fluent/Controls/TabItem.xaml index ebe6f82917..110ffc2f48 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TabItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TabItem.xaml @@ -1,132 +1,133 @@ - - - - - - - - - - + + + + + + + + + + 48 24 2 - - + + + + + + + + + + + + + + + + + + + - - - + + + - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - - + + + - + - - - - + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/TabStrip.xaml b/src/Avalonia.Themes.Fluent/Controls/TabStrip.xaml index 681ac48850..15c3ba2f37 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TabStrip.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TabStrip.xaml @@ -1,32 +1,34 @@ - - - - - Item 1 - Item 2 - Disabled - - - - - + + + + + Item 1 + Item 2 + Disabled + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml b/src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml index 59f68a1547..5ccd430d33 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml @@ -1,107 +1,108 @@ - - - - - Leaf - Arch - - - - + + + + + Leaf + Arch + + + + 48 2 - - + + + + + + + + + + + + + + + + + + + - + - - - - + + + + - - - - + + + + - - + + - - + + - - + + - - + + - - - + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml index 40d9b11f7c..0fd3758d77 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml @@ -1,4 +1,4 @@ - @@ -10,7 +10,7 @@ TextAlignment="Center"/> - + 0,0,0,4 M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z @@ -36,7 +36,7 @@ - + @@ -73,32 +73,32 @@ VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"> - - - - - + + + + + @@ -159,7 +159,7 @@ - + @@ -237,7 +237,7 @@ - + - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - diff --git a/src/Avalonia.Themes.Fluent/Controls/TitleBar.xaml b/src/Avalonia.Themes.Fluent/Controls/TitleBar.xaml index 4dba5b4ba4..2b97df020f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TitleBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TitleBar.xaml @@ -1,53 +1,55 @@ - - - - - - - + + - + - + - + - + - + - - + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml index e0d54d7232..b53980b792 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml @@ -1,5 +1,5 @@ - + @@ -10,104 +10,111 @@ - 8,5,8,6 - - - - - + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + + - + - - + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml index a2b50a859d..533903bec7 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml @@ -1,288 +1,307 @@ - - + + + + + + + + + + + + + + + + + 0,0,0,6 6 6 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml b/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml index 2d18be91cb..1070f3b68c 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml @@ -1,87 +1,71 @@ - - - - - - Hover Here - - + + + + Hover Here + + + - - - - ToolTip - A control which pops up a hint when a control is hovered - - - ToolTip bottom placement - - - + Padding="50" + ToolTip.Placement="Bottom"> + + + ToolTip + A control which pops up a hint when a control is hovered + + + ToolTip bottom placement + + + - 320 - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml b/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml index 6a4d56ccb7..f28dc66a5f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml @@ -1,20 +1,19 @@ - - - + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/TreeView.xaml b/src/Avalonia.Themes.Fluent/Controls/TreeView.xaml index 656b72e07b..4fcec79f25 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TreeView.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TreeView.xaml @@ -1,28 +1,31 @@ - + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml b/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml index f86b67bb6c..6510832eb3 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml @@ -1,4 +1,4 @@ - @@ -53,7 +53,6 @@ diff --git a/src/Avalonia.Themes.Fluent/Controls/Window.xaml b/src/Avalonia.Themes.Fluent/Controls/Window.xaml index 90963c606c..d78fd76a37 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Window.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Window.xaml @@ -1,27 +1,30 @@ - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/WindowNotificationManager.xaml b/src/Avalonia.Themes.Fluent/Controls/WindowNotificationManager.xaml index 8d14c2d972..d5d5114c1b 100644 --- a/src/Avalonia.Themes.Fluent/Controls/WindowNotificationManager.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/WindowNotificationManager.xaml @@ -1,47 +1,58 @@ - - + + + + + + + + + + + + + + + + + + + + - + - + - + - - + + + From afe3486e9f3ea7a493c7e66ca3e5b59682357dcb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 7 Jul 2022 11:15:41 +0200 Subject: [PATCH 050/240] Remove trailing /template/ selectors. --- .../Controls/ButtonSpinner.xaml | 12 +-- .../Controls/CalendarDatePicker.xaml | 12 +-- .../Controls/CalendarDayButton.xaml | 6 +- .../Controls/CheckBox.xaml | 88 +++++++++---------- .../Controls/ComboBox.xaml | 50 +++++------ .../Controls/Slider.xaml | 56 ++++++------ .../Controls/TimePicker.xaml | 6 +- 7 files changed, 113 insertions(+), 117 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml index a5234f5771..f99f1040e5 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml @@ -87,20 +87,20 @@ - - - - diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml index 83a254be56..eefd9e9b51 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml @@ -106,13 +106,13 @@ - - @@ -152,13 +152,13 @@ - - diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml index b119dd1355..9f476d51bc 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml @@ -84,11 +84,11 @@ - - diff --git a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml index 2eee425cb0..44e1afefa4 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml @@ -63,64 +63,64 @@ - - - - - - - - - - - - @@ -145,64 +145,64 @@ - - - - - - - - - - - - @@ -227,64 +227,64 @@ - - - - - - - - - - - - diff --git a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml index 2dc7439001..8ef8388cf1 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml @@ -144,17 +144,15 @@ - - - + + + @@ -175,51 +173,51 @@ - - - - - - - - - - - - diff --git a/src/Avalonia.Themes.Fluent/Controls/Slider.xaml b/src/Avalonia.Themes.Fluent/Controls/Slider.xaml index b468f7f4ad..c74878e8be 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Slider.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Slider.xaml @@ -283,17 +283,15 @@ - - - + + + @@ -316,70 +314,70 @@ - - - - - - - - - - - - - - diff --git a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml index 69b2d32dda..dc09032a36 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml @@ -82,11 +82,11 @@ --> - - From 671b7ea83e75a2b442a4ee0d5877c0e269dfbc48 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 7 Jul 2022 11:50:33 +0200 Subject: [PATCH 051/240] WIP: HamburgerMenu. --- samples/ControlCatalog/App.xaml | 8 +- .../HamburgerMenu/HamburgerMenu.xaml | 260 +++++++++--------- 2 files changed, 139 insertions(+), 129 deletions(-) diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index d0e1bd885e..c7ad72d2c5 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -5,6 +5,13 @@ x:CompileBindings="True" Name="Avalonia ControlCatalog" x:Class="ControlCatalog.App"> + + + + + + + - diff --git a/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml b/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml index 1d58c465a0..5f8bfe4fb5 100644 --- a/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml +++ b/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml @@ -1,6 +1,6 @@ - + @@ -20,21 +20,84 @@ - - 40 - 220 - 36 - 36 - 32 - 12,0,0,0 - 52,0,0,0 - 212,0,0,0 - 1 1 1 1 #2000, 0 0 1 1 #2fff - 0 0 1 1 #2000 - + 40 + 220 + 36 + 36 + 32 + 12,0,0,0 + 52,0,0,0 + 212,0,0,0 + 1 1 1 1 #2000, 0 0 1 1 #2fff + 0 0 1 1 #2000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + IEnumerable Containers { get; } + /// + /// Gets or sets the theme to be applied to the items in the control. + /// + ControlTheme? ItemContainerTheme { get; set; } + /// /// Gets or sets the data template used to display the items in the control. /// diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs index a76dcbe9c8..8b36b07cec 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs @@ -4,6 +4,7 @@ using System.Linq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Styling; namespace Avalonia.Controls.Generators { @@ -35,6 +36,11 @@ namespace Avalonia.Controls.Generators /// public event EventHandler? Recycled; + /// + /// Gets or sets the theme to be applied to the items in the control. + /// + public ControlTheme? ItemContainerTheme { get; set; } + /// /// Gets or sets the data template used to display the items in the control. /// @@ -190,10 +196,18 @@ namespace Avalonia.Controls.Generators result.SetValue( ContentPresenter.ContentTemplateProperty, ItemTemplate, - BindingPriority.TemplatedParent); + BindingPriority.Style); } } + if (ItemContainerTheme != null) + { + result.SetValue( + StyledElement.ThemeProperty, + ItemContainerTheme, + BindingPriority.TemplatedParent); + } + return result; } diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs index 635f3a7d37..3ff1b0702d 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs @@ -44,28 +44,29 @@ namespace Avalonia.Controls.Generators { var container = item as T; - if (container != null) + if (container is null) { - return container; - } - else - { - var result = new T(); + container = new T(); if (ContentTemplateProperty != null) { - result.SetValue(ContentTemplateProperty, ItemTemplate, BindingPriority.Style); + container.SetValue(ContentTemplateProperty, ItemTemplate, BindingPriority.Style); } - result.SetValue(ContentProperty, item, BindingPriority.Style); + container.SetValue(ContentProperty, item, BindingPriority.Style); if (!(item is IControl)) { - result.DataContext = item; + container.DataContext = item; } + } - return result; + if (ItemContainerTheme != null) + { + container.SetValue(StyledElement.ThemeProperty, ItemContainerTheme, BindingPriority.Style); } + + return container; } /// diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs index 536a5fdd06..4e3deb5552 100644 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs @@ -71,6 +71,11 @@ namespace Avalonia.Controls.Generators var template = GetTreeDataTemplate(item, ItemTemplate); var result = new T(); + if (ItemContainerTheme != null) + { + result.SetValue(Control.ThemeProperty, ItemContainerTheme, BindingPriority.Style); + } + result.SetValue(ContentProperty, template.Build(item), BindingPriority.Style); var itemsSelector = template.ItemsSelector(item); diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 56b0014c05..427f5c8a9b 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -15,6 +15,7 @@ using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Metadata; using Avalonia.VisualTree; +using Avalonia.Styling; namespace Avalonia.Controls { @@ -36,6 +37,12 @@ namespace Avalonia.Controls public static readonly DirectProperty ItemsProperty = AvaloniaProperty.RegisterDirect(nameof(Items), o => o.Items, (o, v) => o.Items = v); + /// + /// Defines the property. + /// + public static readonly StyledProperty ItemContainerThemeProperty = + AvaloniaProperty.Register(nameof(ItemContainerTheme)); + /// /// Defines the property. /// @@ -88,6 +95,7 @@ namespace Avalonia.Controls { _itemContainerGenerator = CreateItemContainerGenerator(); + _itemContainerGenerator.ItemContainerTheme = ItemContainerTheme; _itemContainerGenerator.ItemTemplate = ItemTemplate; _itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e); _itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e); @@ -108,6 +116,15 @@ namespace Avalonia.Controls set { SetAndRaise(ItemsProperty, ref _items, value); } } + /// + /// Gets or sets the that is applied to the container element generated for each item. + /// + public ControlTheme? ItemContainerTheme + { + get { return GetValue(ItemContainerThemeProperty); } + set { SetValue(ItemContainerThemeProperty, value); } + } + /// /// Gets the number of items in . /// @@ -349,6 +366,10 @@ namespace Avalonia.Controls { UpdatePseudoClasses(change.GetNewValue()); } + else if (change.Property == ItemContainerThemeProperty && _itemContainerGenerator is not null) + { + _itemContainerGenerator.ItemContainerTheme = change.GetNewValue(); + } } /// diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index 490b0b3ce3..2e3aa037c2 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -121,6 +121,11 @@ namespace Avalonia.Controls { ItemTemplate = _treeView.ItemTemplate; } + + if (ItemContainerTheme == null && _treeView?.ItemContainerTheme != null) + { + ItemContainerTheme = _treeView.ItemContainerTheme; + } } protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index bfece7871c..f08653a4f8 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -1,15 +1,14 @@ +using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using Avalonia.Collections; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; +using Avalonia.Input; using Avalonia.LogicalTree; -using Avalonia.VisualTree; -using Xunit; -using System.Collections.ObjectModel; +using Avalonia.Styling; using Avalonia.UnitTests; -using Avalonia.Input; -using System.Collections.Generic; +using Xunit; namespace Avalonia.Controls.UnitTests { @@ -62,6 +61,25 @@ namespace Avalonia.Controls.UnitTests Assert.Null(container.TemplatedParent); } + [Fact] + public void Container_Should_Have_Theme_Set_To_ItemContainerTheme() + { + var theme = new ControlTheme(); + var target = new ItemsControl + { + ItemContainerTheme = theme, + }; + + target.Template = GetTemplate(); + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + var container = (ContentPresenter)target.Presenter.Panel.Children[0]; + + Assert.Same(container.Theme, theme); + } + [Fact] public void Container_Should_Have_LogicalParent_Set_To_ItemsControl() { diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index e87990ebb1..afa153a593 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -94,6 +94,50 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Container_Should_Have_Theme_Set_To_ItemContainerTheme() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + var items = new[] { "Foo", "Bar", "Baz " }; + var theme = new ControlTheme(); + var target = new ListBox + { + Template = ListBoxTemplate(), + Items = items, + ItemContainerTheme = theme, + }; + + Prepare(target); + + var container = (ListBoxItem)target.Presenter.Panel.Children[0]; + + Assert.Same(container.Theme, theme); + } + } + + [Fact] + public void Inline_Item_Should_Have_Theme_Set_To_ItemContainerTheme() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + var items = new[] { "Foo", "Bar", "Baz " }; + var theme = new ControlTheme(); + var target = new ListBox + { + Template = ListBoxTemplate(), + Items = new[] { new ListBoxItem() }, + ItemContainerTheme = theme, + }; + + Prepare(target); + + var container = (ListBoxItem)target.Presenter.Panel.Children[0]; + + Assert.Same(container.Theme, theme); + } + } + [Fact] public void LogicalChildren_Should_Be_Set_For_DataTemplate_Generated_Items() { diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 2169b15cad..d784caf2db 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -72,6 +72,35 @@ namespace Avalonia.Controls.UnitTests Assert.All(items, x => Assert.IsType(x.HeaderPresenter.Child)); } + [Fact] + public void Items_Should_Be_Created_Using_ItemConatinerTheme_If_Present() + { + TreeView target; + var theme = new ControlTheme(); + + var root = new TestRoot + { + Child = target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = CreateTestTreeData(), + ItemContainerTheme = theme, + ItemTemplate = new FuncTreeDataTemplate( + (_, __) => new Canvas(), + x => x.Children), + } + }; + + ApplyTemplates(target); + + var items = target.ItemContainerGenerator.Index.Containers + .OfType() + .ToList(); + + Assert.Equal(5, items.Count); + Assert.All(items, x => Assert.Same(theme, x.ItemContainerTheme)); + } + [Fact] public void Root_ItemContainerGenerator_Containers_Should_Be_Root_Containers() { From 209797e5e1375c2c20a335d3bc2cf66810d9b129 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 7 Jul 2022 23:32:23 +0200 Subject: [PATCH 053/240] Ported HamburgerMenu to ControlTheme. --- .../HamburgerMenu/HamburgerMenu.xaml | 89 +++++++++++-------- 1 file changed, 54 insertions(+), 35 deletions(-) diff --git a/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml b/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml index 5f8bfe4fb5..73f8e480f5 100644 --- a/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml +++ b/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml @@ -62,9 +62,28 @@ TextElement.FontWeight="{TemplateBinding FontWeight}" /> + + + + + + + + + + + + + + + + Background="{DynamicResource TabItemHeaderSelectedPipeFill}" + IsVisible="False" + CornerRadius="{DynamicResource ControlCornerRadius}"/> + + + + + + + + - + + + @@ -109,7 +157,8 @@ CompactPaneLength="{StaticResource PaneCompactWidth}" DisplayMode="Inline" IsPaneOpen="True" - OpenPaneLength="{StaticResource PaneExpandWidth}"> + OpenPaneLength="{StaticResource PaneExpandWidth}" + PaneBackground="Transparent"> @@ -123,11 +172,6 @@ ItemTemplate="{TemplateBinding ItemTemplate}" Items="{TemplateBinding Items}" ItemsPanel="{TemplateBinding ItemsPanel}"> - - - @@ -187,7 +232,7 @@ HorizontalAlignment="Left" VerticalAlignment="Top" HorizontalContentAlignment="Center" - Classes="NavigationButton" + Theme="{StaticResource NavigationButton}" CornerRadius="4" IsChecked="{Binding #PART_NavigationPane.IsPaneOpen, Mode=TwoWay}"> @@ -196,18 +241,12 @@ - - @@ -221,26 +260,6 @@ - - From 59c58e567242f9641759ef1f1d5dc8380792e03b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 11:47:26 +0200 Subject: [PATCH 054/240] Don't set Foreground in PathIcon theme. It is more useful if this property inherits by default. --- src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml b/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml index 966c6c2632..b9967a1b44 100644 --- a/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml @@ -9,7 +9,6 @@ - From 052cf699d00e30a51347845789bf20f1cd18965d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 11:47:44 +0200 Subject: [PATCH 055/240] Ported ScrollBar to ControlTheme. --- .../Controls/FluentControls.xaml | 2 +- .../Controls/ScrollBar.xaml | 521 +++++++++--------- 2 files changed, 252 insertions(+), 271 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 286b3e3fcd..d344fedf0a 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -34,6 +34,7 @@ + @@ -64,7 +65,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml index 7d87a3e35a..4544fbe339 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml @@ -1,302 +1,283 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - + - - - - - - - - + + + - + + + - + + + + - - - - - - - - - - - - - - - - - + + + + + + - + - + + + + + + + + + + From 9d0132dc24535b89aed7e04fc8da2cfecad061f6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 11:56:21 +0200 Subject: [PATCH 056/240] Remove unneeded normal state. --- .../Controls/ComboBox.xaml | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml index 8ef8388cf1..b3da7735eb 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml @@ -72,7 +72,8 @@ Background="{DynamicResource ComboBoxBackgroundUnfocused}" BorderBrush="{DynamicResource ComboBoxBackgroundBorderBrushUnfocused}" BorderThickness="{TemplateBinding BorderThickness}" - CornerRadius="{TemplateBinding CornerRadius}" /> + CornerRadius="{TemplateBinding CornerRadius}" + IsVisible="False"/> + VerticalAlignment="Center" + Foreground="{DynamicResource ComboBoxDropDownGlyphForeground}" + Data="M1939 486L2029 576L1024 1581L19 576L109 486L1024 1401L1939 486Z"/> - - - - - + + - - diff --git a/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml index b53980b792..617f3e9c45 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml @@ -10,111 +10,117 @@ - 8,5,8,6 + 8,5,8,6 - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + - - - - - - - - - + + + - + + From c15837b743264282fa070eea4fbc63ebacd3d5c1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 13:55:18 +0200 Subject: [PATCH 058/240] Ported ButtonSpinner to ControlTheme. --- .../Controls/ButtonSpinner.xaml | 228 ++++++++++-------- 1 file changed, 133 insertions(+), 95 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml index f99f1040e5..980ea02a73 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml @@ -3,106 +3,144 @@ xmlns:sys="clr-namespace:System;assembly=netstandard" xmlns:converters="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls" x:CompileBindings="True"> - - - - - - - - - - - - - + + + + + + + + + + + + + + + Error + + + + + + + - - + + + M0,9 L10,0 20,9 19,10 10,2 1,10 z + M0,1 L10,10 20,1 19,0 10,8 1,0 z - M0,9 L10,0 20,9 19,10 10,2 1,10 z - M0,1 L10,10 20,1 19,0 10,8 1,0 z + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + - - + + + + + + + + - + + + + From ddbc9fbfcaf7524fcd7ed03a3f481dab7ecff1bf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 14:01:56 +0200 Subject: [PATCH 059/240] Tweaked CheckBox ControlTheme. --- .../Controls/CheckBox.xaml | 451 +++++++++--------- 1 file changed, 228 insertions(+), 223 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml index 44e1afefa4..9abcf5d32b 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml @@ -1,293 +1,298 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Unchecked + Checked + Indeterminate + Checkbox should wrap its text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + + - + - + + + - - - - - - - - + - - - - - - - - + - - - - - - - - + - + + + - - - - - - - - + + + + - - - - - - - - + - - - - - - - - + - + + From 47215816a22a68fd5c7ba863e78843127ba6e603 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 14:08:03 +0200 Subject: [PATCH 060/240] Validated ComboBox/Item ControlTheme. --- .../Controls/ComboBox.xaml | 416 +++++++++--------- .../Controls/ComboBoxItem.xaml | 153 +++---- 2 files changed, 290 insertions(+), 279 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml index b3da7735eb..ec78bf6ab4 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml @@ -1,217 +1,227 @@  - - - - - Item 1 - Item 2 - - - Item 1 - Item 2 - - - - - - 0,0,0,4 - 15 - 7 - - 12,5,0,7 - 11,5,32,6 - 32 - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Item 1 + Item 2 + + + Item 1 + Item 2 + + + + + + Error + + + + + + + + + 0,0,0,4 + 15 + 7 + + 12,5,0,7 + 11,5,32,6 + 32 + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + - - - - - - - - - - - - - - + UseLayoutRounding="False" + IsHitTestVisible="False" + Height="12" + Width="12" + Margin="0,0,10,0" + HorizontalAlignment="Right" + VerticalAlignment="Center" + Foreground="{DynamicResource ComboBoxDropDownGlyphForeground}" + Data="M1939 486L2029 576L1024 1581L19 576L109 486L1024 1401L1939 486Z"/> + + + + + + + + + + + + + + + + + + - - + + + - - - - - - + - - - - + + - - - - - - + + + + - - - - - - + + + - - + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml b/src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml index 88f9c0ea15..581cbaf80a 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml @@ -1,85 +1,86 @@  - - - - - Item 1 - Item 2 long - Item 3 - Item 4 - - + + + + + Item 1 + Item 2 long + Item 3 + Item 4 + Item 5 + - - - - - - - - - - - - - - - + + + + + + + + + - - + - - - - - - - - - - - - + + - + + From 100978a5bf491b75d89fa0669fcce52a212d021d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 14:09:41 +0200 Subject: [PATCH 061/240] Formatted ContentControl ControlTheme. --- .../Controls/ContentControl.xaml | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ContentControl.xaml b/src/Avalonia.Themes.Fluent/Controls/ContentControl.xaml index 5777603074..4caf156366 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ContentControl.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ContentControl.xaml @@ -1,19 +1,19 @@ - - - - - - - + + + + + + + From e89060fbc3c655340fc254ca26744ab9ddcd3ab9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 14:18:48 +0200 Subject: [PATCH 062/240] Formatted DataValidationErrors ControlTheme. --- .../Controls/DataValidationErrors.xaml | 198 +++++++++--------- 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml b/src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml index 6ed73148a7..cc55f84d80 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml @@ -1,103 +1,103 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From ebe9d6b086c3da15678aea2408412df18ce520e7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 14:29:30 +0200 Subject: [PATCH 063/240] Tidied up DropDownButton ControlTheme. --- .../Controls/DropDownButton.xaml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/DropDownButton.xaml b/src/Avalonia.Themes.Fluent/Controls/DropDownButton.xaml index 3206cb8cb1..96fd575f4f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DropDownButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DropDownButton.xaml @@ -57,26 +57,26 @@ Margin="0,0,10,0" Data="M1939 486L2029 576L1024 1581L19 576L109 486L1024 1401L1939 486Z" HorizontalAlignment="Right" - VerticalAlignment="Center" /> + VerticalAlignment="Center" + Foreground="{DynamicResource ComboBoxDropDownGlyphForeground}"/> - - - - + + + - - From 6f797a9c56f643cc724521e75d5ba9ba9a3882bf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 14:30:04 +0200 Subject: [PATCH 064/240] Tidied up EmbeddableControlRoot ControlTheme. --- .../Controls/EmbeddableControlRoot.xaml | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml index 89eaec70ad..f8b4854553 100644 --- a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml @@ -1,23 +1,22 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + From 4f3fafc9dfa6835ed1707715eabd0665dda9bb52 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 14:56:36 +0200 Subject: [PATCH 065/240] Tweaked Expander ControlTheme. And added "Rounded" option to ControlCatalog for testing. --- .../ControlCatalog/Pages/ExpanderPage.xaml | 15 ++-- .../ControlCatalog/Pages/ExpanderPage.xaml.cs | 2 + .../ViewModels/ExpanderPageViewModel.cs | 27 +++++++ .../Controls/Expander.xaml | 71 +++++++++---------- 4 files changed, 72 insertions(+), 43 deletions(-) create mode 100644 samples/ControlCatalog/ViewModels/ExpanderPageViewModel.cs diff --git a/samples/ControlCatalog/Pages/ExpanderPage.xaml b/samples/ControlCatalog/Pages/ExpanderPage.xaml index cef473af04..f9ae1c7a3c 100644 --- a/samples/ControlCatalog/Pages/ExpanderPage.xaml +++ b/samples/ControlCatalog/Pages/ExpanderPage.xaml @@ -8,26 +8,31 @@ Margin="0,16,0,0" HorizontalAlignment="Center" Spacing="16"> - + Expanded content - + Expanded content - + Expanded content - + Expanded content - + Rounded + diff --git a/samples/ControlCatalog/Pages/ExpanderPage.xaml.cs b/samples/ControlCatalog/Pages/ExpanderPage.xaml.cs index 52166d1a5f..e8a080899a 100644 --- a/samples/ControlCatalog/Pages/ExpanderPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ExpanderPage.xaml.cs @@ -1,5 +1,6 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; +using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { @@ -8,6 +9,7 @@ namespace ControlCatalog.Pages public ExpanderPage() { this.InitializeComponent(); + DataContext = new ExpanderPageViewModel(); } private void InitializeComponent() diff --git a/samples/ControlCatalog/ViewModels/ExpanderPageViewModel.cs b/samples/ControlCatalog/ViewModels/ExpanderPageViewModel.cs new file mode 100644 index 0000000000..18511a6071 --- /dev/null +++ b/samples/ControlCatalog/ViewModels/ExpanderPageViewModel.cs @@ -0,0 +1,27 @@ +using Avalonia; +using MiniMvvm; + +namespace ControlCatalog.ViewModels +{ + public class ExpanderPageViewModel : ViewModelBase + { + private CornerRadius _cornerRadius; + private bool _rounded; + + public CornerRadius CornerRadius + { + get => _cornerRadius; + private set => RaiseAndSetIfChanged(ref _cornerRadius, value); + } + + public bool Rounded + { + get => _rounded; + set + { + if (RaiseAndSetIfChanged(ref _rounded, value)) + CornerRadius = _rounded ? new CornerRadius(25) : default; + } + } + } +} diff --git a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml index 36f6008782..7d5bb48333 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml @@ -3,29 +3,39 @@ x:CompileBindings="True"> - - - - - - - - + + Expanded content - + Expanded content - + Expanded content - + + + Expanded content + + + + + + + + + + + Expanded content + + + Expanded content @@ -36,25 +46,19 @@ 16 16 - 1 - 1,1,0,1 1,1,1,0 0,1,1,1 1,0,1,1 - - - - - + - - @@ -186,28 +190,28 @@ - - - - @@ -244,7 +240,6 @@ - From c246d76a206df3b8325006d88ef8aa7891772e1a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 14:58:15 +0200 Subject: [PATCH 066/240] Formated ItemsControl ControlTheme. --- .../Controls/ItemsControl.xaml | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ItemsControl.xaml b/src/Avalonia.Themes.Fluent/Controls/ItemsControl.xaml index b8d7c2c4ef..fc85b7b9ff 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ItemsControl.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ItemsControl.xaml @@ -1,19 +1,19 @@ - - - - - - - - - + + + + + + + + + From 4d73e2b3a946fa65db1df2c7b7c222e0c1b6410a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 14:58:40 +0200 Subject: [PATCH 067/240] Formatted Label ControlTheme. --- .../Controls/Label.xaml | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/Label.xaml b/src/Avalonia.Themes.Fluent/Controls/Label.xaml index ad57239648..ff6909ee87 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Label.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Label.xaml @@ -1,22 +1,22 @@ - - - - - - - - + + + + + + + + From 901026a187d03fa020c7c503efb443f855cc32f8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 15:01:03 +0200 Subject: [PATCH 068/240] Formatted ListBox/Item ControlTheme. --- .../Controls/ListBox.xaml | 88 ++++++------ .../Controls/ListBoxItem.xaml | 132 +++++++++--------- 2 files changed, 110 insertions(+), 110 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml index b1d4b77a3f..c17dc65cca 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml @@ -1,47 +1,47 @@  - - - - Test - Test - Test - Test - - - - - - - - - - - - - - - - - - - - - - - + + + + Test + Test + Test + Test + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml b/src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml index c00e920f75..fc1d0c5ff1 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml @@ -1,79 +1,79 @@  - - - - Disabled - - Test - - Test - - - - 12,9,12,12 - - - - - - - - + + + + Disabled + + Test + + Test + + + + 12,9,12,12 + + + + + + + + - + - - + + + + + + - - - - - - - - - - + + - - + + - + + From 800967c62742ca9934f47d6e68be94095b1241c1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 15:02:52 +0200 Subject: [PATCH 069/240] Formatted Menu/FlyoutPresenter ControlTheme. --- src/Avalonia.Themes.Fluent/Controls/Menu.xaml | 62 ++++++++--------- .../Controls/MenuFlyoutPresenter.xaml | 67 ++++++++++--------- 2 files changed, 65 insertions(+), 64 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/Menu.xaml b/src/Avalonia.Themes.Fluent/Controls/Menu.xaml index d42e413f00..11d21b6b23 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Menu.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Menu.xaml @@ -1,35 +1,35 @@ - - - - - - - - + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + + + + + + + + - 32 + 32 - - - - - - - - - - - + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/MenuFlyoutPresenter.xaml b/src/Avalonia.Themes.Fluent/Controls/MenuFlyoutPresenter.xaml index 7769f0a5c9..264438390d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/MenuFlyoutPresenter.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/MenuFlyoutPresenter.xaml @@ -1,34 +1,35 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + From f82541a90d4916f192e432c76fa329d4d3dd3dbf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 15:09:47 +0200 Subject: [PATCH 070/240] Tweaked NotificationCard ControlTheme. --- .../Accents/FluentControlResourcesDark.xaml | 1 + .../Accents/FluentControlResourcesLight.xaml | 1 + .../Controls/NotificationCard.xaml | 82 +++++++++---------- 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index c5bb70bed3..9d04009aed 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -308,6 +308,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index 8d38d39bd5..7b673767b8 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -304,6 +304,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml b/src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml index e55fcb90e8..51dcdd42e3 100644 --- a/src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml @@ -9,7 +9,7 @@ - + @@ -53,47 +53,47 @@ - + - + - - - - + + + + From db954bfd0ebf0401a9bc1cfc6dc517cd1e2d5a08 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 15:11:10 +0200 Subject: [PATCH 071/240] Formatted NumericUpDown ControlTheme. --- .../Controls/NumericUpDown.xaml | 126 +++++++++--------- 1 file changed, 62 insertions(+), 64 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml b/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml index 74ffff0f5c..872367c6da 100644 --- a/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml @@ -1,67 +1,65 @@  - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + From 474df23c0715253fe3be730fc1cdd59d093a2749 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 15:11:35 +0200 Subject: [PATCH 072/240] Formatted OverlayPopupHost ControlTheme. --- .../Controls/OverlayPopupHost.xaml | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml b/src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml index 0b587e6037..28d8fb508c 100644 --- a/src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml @@ -1,23 +1,23 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + From 0d7bb472b38e042e44b1200f0f08e0c405f6c353 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 15:12:20 +0200 Subject: [PATCH 073/240] Formatted PathIcon ControlTheme. --- .../Controls/PathIcon.xaml | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml b/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml index b9967a1b44..5584eb3ab9 100644 --- a/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml @@ -1,25 +1,25 @@  - - - - M14 9.50006C11.5147 9.50006 9.5 11.5148 9.5 14.0001C9.5 16.4853 11.5147 18.5001 14 18.5001C15.3488 18.5001 16.559 17.9066 17.3838 16.9666C18.0787 16.1746 18.5 15.1365 18.5 14.0001C18.5 13.5401 18.431 13.0963 18.3028 12.6784C17.7382 10.8381 16.0253 9.50006 14 9.50006ZM11 14.0001C11 12.3432 12.3431 11.0001 14 11.0001C15.6569 11.0001 17 12.3432 17 14.0001C17 15.6569 15.6569 17.0001 14 17.0001C12.3431 17.0001 11 15.6569 11 14.0001Z M21.7093 22.3948L19.9818 21.6364C19.4876 21.4197 18.9071 21.4515 18.44 21.7219C17.9729 21.9924 17.675 22.4693 17.6157 23.0066L17.408 24.8855C17.3651 25.273 17.084 25.5917 16.7055 25.682C14.9263 26.1061 13.0725 26.1061 11.2933 25.682C10.9148 25.5917 10.6336 25.273 10.5908 24.8855L10.3834 23.0093C10.3225 22.4731 10.0112 21.9976 9.54452 21.7281C9.07783 21.4586 8.51117 21.4269 8.01859 21.6424L6.29071 22.4009C5.93281 22.558 5.51493 22.4718 5.24806 22.1859C4.00474 20.8536 3.07924 19.2561 2.54122 17.5137C2.42533 17.1384 2.55922 16.7307 2.8749 16.4977L4.40219 15.3703C4.83721 15.0501 5.09414 14.5415 5.09414 14.0007C5.09414 13.4598 4.83721 12.9512 4.40162 12.6306L2.87529 11.5051C2.55914 11.272 2.42513 10.8638 2.54142 10.4882C3.08038 8.74734 4.00637 7.15163 5.24971 5.82114C5.51684 5.53528 5.93492 5.44941 6.29276 5.60691L8.01296 6.36404C8.50793 6.58168 9.07696 6.54881 9.54617 6.27415C10.0133 6.00264 10.3244 5.52527 10.3844 4.98794L10.5933 3.11017C10.637 2.71803 10.9245 2.39704 11.3089 2.31138C12.19 2.11504 13.0891 2.01071 14.0131 2.00006C14.9147 2.01047 15.8128 2.11485 16.6928 2.31149C17.077 2.39734 17.3643 2.71823 17.4079 3.11017L17.617 4.98937C17.7116 5.85221 18.4387 6.50572 19.3055 6.50663C19.5385 6.507 19.769 6.45838 19.9843 6.36294L21.7048 5.60568C22.0626 5.44818 22.4807 5.53405 22.7478 5.81991C23.9912 7.1504 24.9172 8.74611 25.4561 10.487C25.5723 10.8623 25.4386 11.2703 25.1228 11.5035L23.5978 12.6297C23.1628 12.95 22.9 13.4586 22.9 13.9994C22.9 14.5403 23.1628 15.0489 23.5988 15.3698L25.1251 16.4965C25.441 16.7296 25.5748 17.1376 25.4586 17.5131C24.9198 19.2536 23.9944 20.8492 22.7517 22.1799C22.4849 22.4657 22.0671 22.5518 21.7093 22.3948ZM16.263 22.1966C16.4982 21.4685 16.9889 20.8288 17.6884 20.4238C18.5702 19.9132 19.6536 19.8547 20.5841 20.2627L21.9281 20.8526C22.791 19.8538 23.4593 18.7013 23.8981 17.4552L22.7095 16.5778L22.7086 16.5771C21.898 15.98 21.4 15.0277 21.4 13.9994C21.4 12.9719 21.8974 12.0195 22.7073 11.4227L22.7085 11.4218L23.8957 10.545C23.4567 9.2988 22.7881 8.14636 21.9248 7.1477L20.5922 7.73425L20.5899 7.73527C20.1844 7.91463 19.7472 8.00722 19.3039 8.00663C17.6715 8.00453 16.3046 6.77431 16.1261 5.15465L16.1259 5.15291L15.9635 3.69304C15.3202 3.57328 14.6677 3.50872 14.013 3.50017C13.3389 3.50891 12.6821 3.57367 12.0377 3.69328L11.8751 5.15452C11.7625 6.16272 11.1793 7.05909 10.3019 7.56986C9.41937 8.0856 8.34453 8.14844 7.40869 7.73694L6.07273 7.14893C5.20949 8.14751 4.54092 9.29983 4.10196 10.5459L5.29181 11.4233C6.11115 12.0269 6.59414 12.9837 6.59414 14.0007C6.59414 15.0173 6.11142 15.9742 5.29237 16.5776L4.10161 17.4566C4.54002 18.7044 5.2085 19.8585 6.07205 20.8587L7.41742 20.2682C8.34745 19.8613 9.41573 19.9215 10.2947 20.4292C11.174 20.937 11.7593 21.832 11.8738 22.84L11.8744 22.8445L12.0362 24.3088C13.3326 24.5638 14.6662 24.5638 15.9626 24.3088L16.1247 22.8418C16.1491 22.6217 16.1955 22.4055 16.263 22.1966Z - - - - - - - - - - - - - - - - - - + + + + M14 9.50006C11.5147 9.50006 9.5 11.5148 9.5 14.0001C9.5 16.4853 11.5147 18.5001 14 18.5001C15.3488 18.5001 16.559 17.9066 17.3838 16.9666C18.0787 16.1746 18.5 15.1365 18.5 14.0001C18.5 13.5401 18.431 13.0963 18.3028 12.6784C17.7382 10.8381 16.0253 9.50006 14 9.50006ZM11 14.0001C11 12.3432 12.3431 11.0001 14 11.0001C15.6569 11.0001 17 12.3432 17 14.0001C17 15.6569 15.6569 17.0001 14 17.0001C12.3431 17.0001 11 15.6569 11 14.0001Z M21.7093 22.3948L19.9818 21.6364C19.4876 21.4197 18.9071 21.4515 18.44 21.7219C17.9729 21.9924 17.675 22.4693 17.6157 23.0066L17.408 24.8855C17.3651 25.273 17.084 25.5917 16.7055 25.682C14.9263 26.1061 13.0725 26.1061 11.2933 25.682C10.9148 25.5917 10.6336 25.273 10.5908 24.8855L10.3834 23.0093C10.3225 22.4731 10.0112 21.9976 9.54452 21.7281C9.07783 21.4586 8.51117 21.4269 8.01859 21.6424L6.29071 22.4009C5.93281 22.558 5.51493 22.4718 5.24806 22.1859C4.00474 20.8536 3.07924 19.2561 2.54122 17.5137C2.42533 17.1384 2.55922 16.7307 2.8749 16.4977L4.40219 15.3703C4.83721 15.0501 5.09414 14.5415 5.09414 14.0007C5.09414 13.4598 4.83721 12.9512 4.40162 12.6306L2.87529 11.5051C2.55914 11.272 2.42513 10.8638 2.54142 10.4882C3.08038 8.74734 4.00637 7.15163 5.24971 5.82114C5.51684 5.53528 5.93492 5.44941 6.29276 5.60691L8.01296 6.36404C8.50793 6.58168 9.07696 6.54881 9.54617 6.27415C10.0133 6.00264 10.3244 5.52527 10.3844 4.98794L10.5933 3.11017C10.637 2.71803 10.9245 2.39704 11.3089 2.31138C12.19 2.11504 13.0891 2.01071 14.0131 2.00006C14.9147 2.01047 15.8128 2.11485 16.6928 2.31149C17.077 2.39734 17.3643 2.71823 17.4079 3.11017L17.617 4.98937C17.7116 5.85221 18.4387 6.50572 19.3055 6.50663C19.5385 6.507 19.769 6.45838 19.9843 6.36294L21.7048 5.60568C22.0626 5.44818 22.4807 5.53405 22.7478 5.81991C23.9912 7.1504 24.9172 8.74611 25.4561 10.487C25.5723 10.8623 25.4386 11.2703 25.1228 11.5035L23.5978 12.6297C23.1628 12.95 22.9 13.4586 22.9 13.9994C22.9 14.5403 23.1628 15.0489 23.5988 15.3698L25.1251 16.4965C25.441 16.7296 25.5748 17.1376 25.4586 17.5131C24.9198 19.2536 23.9944 20.8492 22.7517 22.1799C22.4849 22.4657 22.0671 22.5518 21.7093 22.3948ZM16.263 22.1966C16.4982 21.4685 16.9889 20.8288 17.6884 20.4238C18.5702 19.9132 19.6536 19.8547 20.5841 20.2627L21.9281 20.8526C22.791 19.8538 23.4593 18.7013 23.8981 17.4552L22.7095 16.5778L22.7086 16.5771C21.898 15.98 21.4 15.0277 21.4 13.9994C21.4 12.9719 21.8974 12.0195 22.7073 11.4227L22.7085 11.4218L23.8957 10.545C23.4567 9.2988 22.7881 8.14636 21.9248 7.1477L20.5922 7.73425L20.5899 7.73527C20.1844 7.91463 19.7472 8.00722 19.3039 8.00663C17.6715 8.00453 16.3046 6.77431 16.1261 5.15465L16.1259 5.15291L15.9635 3.69304C15.3202 3.57328 14.6677 3.50872 14.013 3.50017C13.3389 3.50891 12.6821 3.57367 12.0377 3.69328L11.8751 5.15452C11.7625 6.16272 11.1793 7.05909 10.3019 7.56986C9.41937 8.0856 8.34453 8.14844 7.40869 7.73694L6.07273 7.14893C5.20949 8.14751 4.54092 9.29983 4.10196 10.5459L5.29181 11.4233C6.11115 12.0269 6.59414 12.9837 6.59414 14.0007C6.59414 15.0173 6.11142 15.9742 5.29237 16.5776L4.10161 17.4566C4.54002 18.7044 5.2085 19.8585 6.07205 20.8587L7.41742 20.2682C8.34745 19.8613 9.41573 19.9215 10.2947 20.4292C11.174 20.937 11.7593 21.832 11.8738 22.84L11.8744 22.8445L12.0362 24.3088C13.3326 24.5638 14.6662 24.5638 15.9626 24.3088L16.1247 22.8418C16.1491 22.6217 16.1955 22.4055 16.263 22.1966Z + + + + + + + + + + + + + + + + + + From 8b69e083e8968df9361378454ddad87b834159f2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 15:12:54 +0200 Subject: [PATCH 074/240] Formatted PopupRoot ControlTheme. --- .../Controls/PopupRoot.xaml | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml index f57ecd2013..5a924830b1 100644 --- a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml @@ -1,28 +1,28 @@  - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + From 3dc70c0725526db91f6f9c765ba212d363ef1514 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 15:17:15 +0200 Subject: [PATCH 075/240] Checked ProgressBar ControlTheme. --- .../Controls/ProgressBar.xaml | 348 +++++++++--------- 1 file changed, 176 insertions(+), 172 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml index 3c7d22e13f..6bf813385d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml @@ -2,178 +2,182 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:CompileBindings="True"> - - + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d284822731e941224c76f0bf9eda3fa1349f3470 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 15:20:16 +0200 Subject: [PATCH 076/240] Tweaked RepeatButton ControlTheme. --- .../Controls/RepeatButton.xaml | 110 +++++++++--------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml b/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml index 670212c4b1..5e789e7e10 100644 --- a/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml @@ -1,65 +1,63 @@  - - - - - - - - - 8,5,8,6 + + + + + + + + + - - - - - - - - - - - - - - - - + 8,5,8,6 - + + + + + + + + + + + + + + + + - + - + - + - - + + From 250b5b91ef8c5d4d8fefd66c14029ee0720fe0ec Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 16:02:03 +0200 Subject: [PATCH 077/240] Tweaked Slider ControlTheme. --- .../Controls/Slider.xaml | 534 +++++++++--------- 1 file changed, 269 insertions(+), 265 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/Slider.xaml b/src/Avalonia.Themes.Fluent/Controls/Slider.xaml index c74878e8be..9378640d64 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Slider.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Slider.xaml @@ -1,57 +1,71 @@ - - - - - - - - - - - - - - - - 0,0,0,4 - 15 - 15 - 32 - 32 - 10 - 20 - 20 - 20 - 20 - + + + + + + + + + + + + + Error + + + + + + + + + + + + + + + 0,0,0,4 + 15 + 15 + 32 + 32 + 10 + 20 + 20 + 20 + 20 - + - - + + - - - - - - - - - + + + + + + + + + + @@ -65,225 +79,215 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -326,7 +330,7 @@ - @@ -344,18 +348,18 @@ - - + @@ -370,14 +374,14 @@ - From e5f70deab03fea424de8b06a98c4e50f23eecde6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 16:04:37 +0200 Subject: [PATCH 078/240] Formatted TabControl ControlTheme. --- .../Controls/TabControl.xaml | 111 +++++++++--------- 1 file changed, 54 insertions(+), 57 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/TabControl.xaml b/src/Avalonia.Themes.Fluent/Controls/TabControl.xaml index 1cc31f8e15..17d179e0cd 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TabControl.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TabControl.xaml @@ -1,63 +1,60 @@  - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - 0 0 0 2 + 0 0 0 2 - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - + + + + From 27abe8200987ac1ab627549f3642d1f9353e9e18 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 16:15:06 +0200 Subject: [PATCH 079/240] Twaked TabItem ControlTheme. --- .../Controls/TabItem.xaml | 230 +++++++++--------- 1 file changed, 113 insertions(+), 117 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/TabItem.xaml b/src/Avalonia.Themes.Fluent/Controls/TabItem.xaml index 110ffc2f48..e323be72e2 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TabItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TabItem.xaml @@ -2,132 +2,128 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:CompileBindings="True"> - - - - - - - - - - 48 - 24 - 2 + + + + + + + + + - - - - - - - - - - - - - - - - - - - + 48 + 24 + 2 - - - + + + + + + + + + + + + + + + + + + + - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - - + + + - + - - - - + + + + From 1af445893abf5d66af80bedd05d932e8e0febe44 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 16:20:33 +0200 Subject: [PATCH 080/240] Tweaked TabStrip/Item ControlTemplates. --- .../Controls/TabStrip.xaml | 62 +++--- .../Controls/TabStripItem.xaml | 180 +++++++++--------- 2 files changed, 116 insertions(+), 126 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/TabStrip.xaml b/src/Avalonia.Themes.Fluent/Controls/TabStrip.xaml index 15c3ba2f37..59cb7f6752 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TabStrip.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TabStrip.xaml @@ -1,34 +1,34 @@  - - - - Item 1 - Item 2 - Disabled - - - - - - - - - - - - - - - - - - + + + + Item 1 + Item 2 + Disabled + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml b/src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml index 5ccd430d33..fab8ba87aa 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml @@ -2,107 +2,97 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:CompileBindings="True"> - - - - Leaf - Arch - - - - - 48 - 2 + + + + Leaf + Arch + Background + + + - - - - - - - - - - - - - - - - - - - + 48 + 2 - + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - + + - - + + - - + + - - + + - - - - - - + + + From c27f5e798e0bb1a15491c931abc3330fa3e17d4e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 16:24:51 +0200 Subject: [PATCH 081/240] Tweaked ToggleButton ControlTheme. --- src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml index 617f3e9c45..36c0c50064 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml @@ -45,10 +45,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + 0,0,0,6 + 6 + 6 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From e8091cfe80759251c7fe35f834c91fe606b8576c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 16:48:04 +0200 Subject: [PATCH 083/240] Tweaked ToolTip ControlTheme. --- .../Controls/ToolTip.xaml | 113 ++++++++---------- 1 file changed, 50 insertions(+), 63 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml b/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml index 1070f3b68c..bc9dceb545 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml @@ -2,70 +2,57 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=netstandard" x:CompileBindings="True"> - - - - Hover Here - - - - - - ToolTip - A control which pops up a hint when a control is hovered - - - ToolTip bottom placement - - - + + + + Text Content + Very long text content which should exceed the maximum with of the tooltip and wrap. + + + Multi-line + Control Content + + + + + - 320 + 320 - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - + + From 433325e7463376756de70912a9fac103c958d54c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 16:48:44 +0200 Subject: [PATCH 084/240] Formatted TransitioningContentControl ControlTheme. --- .../Controls/TransitioningContentControl.xaml | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml b/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml index f28dc66a5f..f1971fe236 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml @@ -1,19 +1,19 @@  - - - - - - - + + + + + + + From 1bbff64d1fffd557c031e5fc4e78b003fdfe2c35 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 16:50:39 +0200 Subject: [PATCH 085/240] Added preview to TreeView ControlTheme. --- .../Controls/TreeView.xaml | 66 +++++++++++-------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/TreeView.xaml b/src/Avalonia.Themes.Fluent/Controls/TreeView.xaml index 4fcec79f25..c340599000 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TreeView.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TreeView.xaml @@ -1,31 +1,43 @@ + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + From 42da6cde7c1d237161768939c032bbf8a54b3f53 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 16:56:49 +0200 Subject: [PATCH 086/240] Formatted Window ControlTheme. --- .../Controls/Window.xaml | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/Window.xaml b/src/Avalonia.Themes.Fluent/Controls/Window.xaml index d78fd76a37..35cc81663f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Window.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Window.xaml @@ -1,30 +1,30 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + From fa65cae7d82d6d3769d9a74f46a0033ea9852967 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 16:58:05 +0200 Subject: [PATCH 087/240] Formatted WindowNotificationManager ControlTheme. --- .../Controls/WindowNotificationManager.xaml | 105 +++++++++--------- 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/WindowNotificationManager.xaml b/src/Avalonia.Themes.Fluent/Controls/WindowNotificationManager.xaml index d5d5114c1b..77f49941f0 100644 --- a/src/Avalonia.Themes.Fluent/Controls/WindowNotificationManager.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/WindowNotificationManager.xaml @@ -1,58 +1,57 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + - + - + - - + + From 5229cf57336c771ecda45dfea704340a7d1141e3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 17:00:12 +0200 Subject: [PATCH 088/240] Formatted Calendar ControlTheme. --- .../Controls/Calendar.xaml | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml b/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml index 3434f9b6e5..8d54cb93ca 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml @@ -5,36 +5,36 @@ // All other rights reserved. --> - - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + From 7af2eb9a29e0ea5a2b8b08b3a00b72518b34b6ef Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 17:08:40 +0200 Subject: [PATCH 089/240] Formatted CalendarButton ControlTheme. --- .../Controls/CalendarButton.xaml | 178 +++++++++--------- 1 file changed, 93 insertions(+), 85 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml index 0381022910..d1aee7ee9a 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml @@ -4,106 +4,114 @@ // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - + + + + + - - - - - - + - - - - + + - - - + - - - - + - - - - + + + + + + From f66d9aca7a7c034ecdc6ec1565176790677c1c39 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 17:10:48 +0200 Subject: [PATCH 090/240] Formatted CalendarDayButton ControlTheme. --- .../Controls/CalendarDayButton.xaml | 172 +++++++++--------- 1 file changed, 90 insertions(+), 82 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml index 9f476d51bc..dd2692cc5a 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml @@ -6,99 +6,107 @@ --> - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + - - - - - - + + - - - - + - - + - - + + + + + + + From 1e642fb3218d990cc8bbbe84e22c8f8d336ca52b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 17:23:24 +0200 Subject: [PATCH 091/240] Tweaked CalendarItem ControlTheme. --- .../Controls/CalendarItem.xaml | 276 +++++++++--------- 1 file changed, 143 insertions(+), 133 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml index f8fec58ef7..6914a0db08 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml @@ -6,145 +6,155 @@ --> - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + 2000-01-01 + 2000-01-05 + + + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + From f6e8dda027e159820f744a03ebff8cca80d1c998 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 8 Jul 2022 17:26:17 +0200 Subject: [PATCH 092/240] Formatted CalendarDatePicker ControlTheme. --- .../Controls/CalendarDatePicker.xaml | 328 +++++++++--------- 1 file changed, 163 insertions(+), 165 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml index eefd9e9b51..54f6a200b8 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml @@ -2,179 +2,177 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=netstandard" x:CompileBindings="True"> - - - - - - - 12 - 32 + + + + + - - - - - - - - - - - 12 + 32 + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + - - - + + + + + + + + + + + From 83140fe427b8ddcb691b7dc063e5af965de40180 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 9 Jul 2022 00:18:28 +0200 Subject: [PATCH 097/240] Reset button animations correctly. Otherwise they keep on animating way after the interaction. --- src/Avalonia.Themes.Fluent/Controls/Button.xaml | 1 + src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml | 1 + src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml | 1 + 3 files changed, 3 insertions(+) diff --git a/src/Avalonia.Themes.Fluent/Controls/Button.xaml b/src/Avalonia.Themes.Fluent/Controls/Button.xaml index 7ac5c4351a..7828fd52ed 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Button.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Button.xaml @@ -20,6 +20,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml b/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml index 5e789e7e10..a54187104b 100644 --- a/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml @@ -23,6 +23,7 @@ + + From 0d5b10ec3aa041b21d5cd4a785b37201dec03f7a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 9 Jul 2022 00:25:04 +0200 Subject: [PATCH 098/240] Tweaked DatePicker ControlTheme. --- .../Controls/DatePicker.xaml | 629 +++++++++--------- 1 file changed, 317 insertions(+), 312 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml index a7093c1341..0537feb60b 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml @@ -9,344 +9,349 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=netstandard" x:CompileBindings="True"> - - - - - + + + + + - 0,0,0,4 - 40 - 40 - 41 - 296 - 456 - 0,3,0,6 - 9,3,0,6 - 0,3,0,6 - 9,3,0,6 - 1 + 0,0,0,4 + 40 + 40 + 41 + 296 + 456 + 0,3,0,6 + 9,3,0,6 + 0,3,0,6 + 9,3,0,6 + 1 - - - - - - - - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - + - - + - - - - - - - - - - + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + - + + + + + + + + + + + + + + - - - + - - - - + + + - + + + + - - - + - + + + - + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + - + - - - + + + From c4541e27db84734a963dc785060ca023a2606283 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 9 Jul 2022 00:48:00 +0200 Subject: [PATCH 099/240] Ported ManagedFileChooser to ControlTheme. --- .../Controls/FluentControls.xaml | 4 +- .../Controls/ManagedFileChooser.xaml | 156 +++++++++--------- 2 files changed, 82 insertions(+), 78 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 0b5f6a4a72..1c936ba097 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -59,6 +59,8 @@ + + @@ -72,7 +74,5 @@ - - diff --git a/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml b/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml index dc454e4fdb..55f4893057 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml @@ -1,8 +1,14 @@ - - + - + + + + - - - - - - - - - - + + + - - - + + + From da264ac4d0587b7cd6bd36cf0181caafacd83642 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 9 Jul 2022 02:20:14 +0200 Subject: [PATCH 100/240] WIP: Menu/Item. Something strange is happening when you open the menu a 3rd time. --- .../Controls/FluentControls.xaml | 2 +- src/Avalonia.Themes.Fluent/Controls/Menu.xaml | 82 ++++- .../Controls/MenuItem.xaml | 303 +++++++----------- 3 files changed, 191 insertions(+), 196 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 1c936ba097..6d51dac17f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -26,6 +26,7 @@ + @@ -67,7 +68,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Controls/Menu.xaml b/src/Avalonia.Themes.Fluent/Controls/Menu.xaml index 11d21b6b23..3e0e81f870 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Menu.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Menu.xaml @@ -3,17 +3,95 @@ - - + + + 32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + 😊 + - - - - + + + + - - + + + + + - - + + - + - - - -4 - 0,0,12,0 - 24,0,0,0 - M 1,0 10,10 l -9,10 -1,-1 L 8,10 -0,1 Z - + + -4 + 0,0,12,0 + 24,0,0,0 + M 1,0 10,10 l -9,10 -1,-1 L 8,10 -0,1 Z - - - - - - - - - - - - - - - - - + - - - - - + + + + + - - - - + + + + + - + + + + + - + - + - - + + + From d2e4d58f178113d500ca5dffc06734be751ce403 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 9 Jul 2022 15:17:15 +0200 Subject: [PATCH 101/240] Formatted NativeMenuBar ControlTheme. --- src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml b/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml index 753c03992a..4f51ebfa7f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml @@ -25,5 +25,5 @@ - + From d902c3eb0ca5a7559d1e7843abd93f567fe065bc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 9 Jul 2022 15:53:41 +0200 Subject: [PATCH 102/240] Port ScrollViewer to ControlTheme. Including splitting out the menu scroller into a separate xaml file for separate preview. --- .../Controls/FluentControls.xaml | 1 + .../Controls/MenuItem.xaml | 28 ++- .../Controls/MenuScrollViewer.xaml | 98 +++++++++++ .../Controls/ScrollViewer.xaml | 164 +++++++++--------- 4 files changed, 204 insertions(+), 87 deletions(-) create mode 100644 src/Avalonia.Themes.Fluent/Controls/MenuScrollViewer.xaml diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 6d51dac17f..3e4361465c 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -25,6 +25,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml index 3ec65c4936..8f77d1bc1b 100644 --- a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml @@ -5,8 +5,8 @@ x:DataType="MenuItem" x:CompileBindings="True"> - - + + @@ -22,8 +22,26 @@ - - + + + + + + + + + + + + + + + + + + + + @@ -113,7 +131,7 @@ MinHeight="{DynamicResource MenuFlyoutThemeMinHeight}" HorizontalAlignment="Stretch" CornerRadius="{DynamicResource OverlayCornerRadius}"> - + + + + + + Item 1 + Item 2 + Item 3 + Item 4 + Item 5 + Item 6 + Item 7 + Item 8 + Item 9 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml b/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml index a6e02d2769..55adb54d9b 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml @@ -1,87 +1,87 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Item 1 + Item 2 + Item 3 + Item 4 + Item 5 + Item 6 + Item 7 + Item 8 + Item 9 + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + From 1d15d1b51343137a24171002b6f07ac98517b1af Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 10 Jul 2022 00:04:29 +0200 Subject: [PATCH 103/240] Ported SplitView to ControlTheme. --- .../Controls/FluentControls.xaml | 2 +- .../Controls/SplitView.xaml | 444 +++++++++--------- 2 files changed, 231 insertions(+), 215 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 3e4361465c..fc89d7c21e 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -39,6 +39,7 @@ + @@ -72,7 +73,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Controls/SplitView.xaml b/src/Avalonia.Themes.Fluent/Controls/SplitView.xaml index 6b9b94852f..9feb6c7fc6 100644 --- a/src/Avalonia.Themes.Fluent/Controls/SplitView.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/SplitView.xaml @@ -1,4 +1,4 @@ - @@ -15,219 +15,235 @@ - - 320 - 48 - 00:00:00.2 - 00:00:00.1 - 0.1,0.9,0.2,1.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From b1038f115b8abdc48ae884ec531a3ae385731010 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 10 Jul 2022 01:31:23 +0200 Subject: [PATCH 104/240] Ported TreeViewItem to ControlTheme. --- .../Controls/FluentControls.xaml | 4 +- .../Controls/TreeViewItem.xaml | 178 +++++++++--------- 2 files changed, 87 insertions(+), 95 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index fc89d7c21e..58c33bf26f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -51,6 +51,7 @@ + @@ -68,10 +69,7 @@ - - - diff --git a/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml b/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml index 6510832eb3..83abe7848c 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml @@ -1,4 +1,4 @@ - @@ -6,32 +6,29 @@ - - + + - + - + - - 16 - 12 - 12, 0, 12, 0 - M 1,0 10,10 l -9,10 -1,-1 L 8,10 -0,1 Z - M0,1 L10,10 20,1 19,0 10,8 1,0 Z - - + 16 + 12 + 12, 0, 12, 0 + M 1,0 10,10 l -9,10 -1,-1 L 8,10 -0,1 Z + M0,1 L10,10 20,1 19,0 10,8 1,0 Z + - - + + + @@ -78,7 +80,7 @@ @@ -98,83 +100,75 @@ - - - - - - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - + + + - + + + + From 7fcd73836940e0bf3654fe4e0a86493eb75e8386 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 10 Jul 2022 02:12:56 +0200 Subject: [PATCH 105/240] Tweaked TitleBar ControlTheme. --- src/Avalonia.Controls/Chrome/TitleBar.cs | 15 ++++++------ .../Controls/TitleBar.xaml | 24 +++++++++++-------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index b152a31587..428bf8f9b0 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -35,7 +35,8 @@ namespace Avalonia.Controls.Chrome } } - IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false; + if (!Design.IsDesignMode) + IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false; } } @@ -44,13 +45,13 @@ namespace Avalonia.Controls.Chrome base.OnApplyTemplate(e); _captionButtons?.Detach(); - + _captionButtons = e.NameScope.Get("PART_CaptionButtons"); - if (VisualRoot is Window window) + if (VisualRoot is Window window && !Design.IsDesignMode) { - _captionButtons?.Attach(window); - + _captionButtons?.Attach(window); + UpdateSize(window); } } @@ -59,7 +60,7 @@ namespace Avalonia.Controls.Chrome { base.OnAttachedToVisualTree(e); - if (VisualRoot is Window window) + if (VisualRoot is Window window && !Design.IsDesignMode) { _disposables = new CompositeDisposable { @@ -90,7 +91,7 @@ namespace Avalonia.Controls.Chrome base.OnDetachedFromVisualTree(e); _disposables?.Dispose(); - + _captionButtons?.Detach(); _captionButtons = null; } diff --git a/src/Avalonia.Themes.Fluent/Controls/TitleBar.xaml b/src/Avalonia.Themes.Fluent/Controls/TitleBar.xaml index 2b97df020f..5730b19e78 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TitleBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TitleBar.xaml @@ -1,8 +1,8 @@  - - + + @@ -13,11 +13,19 @@ - - + + - - + + @@ -27,10 +35,6 @@ - - From 5864452d75a596bdd7bd0d0cdd6de8f3805d8159 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 10 Jul 2022 15:19:40 +0200 Subject: [PATCH 106/240] Undo changed to TitleBar. It was causing the title bar to be shown in all designer previews. --- src/Avalonia.Controls/Chrome/TitleBar.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index 428bf8f9b0..1bf13111a9 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -35,8 +35,7 @@ namespace Avalonia.Controls.Chrome } } - if (!Design.IsDesignMode) - IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false; + IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false; } } @@ -48,7 +47,7 @@ namespace Avalonia.Controls.Chrome _captionButtons = e.NameScope.Get("PART_CaptionButtons"); - if (VisualRoot is Window window && !Design.IsDesignMode) + if (VisualRoot is Window window) { _captionButtons?.Attach(window); @@ -60,7 +59,7 @@ namespace Avalonia.Controls.Chrome { base.OnAttachedToVisualTree(e); - if (VisualRoot is Window window && !Design.IsDesignMode) + if (VisualRoot is Window window) { _disposables = new CompositeDisposable { From 07cce63b8bd4b009bd384e727caaab16d339bc4a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 10 Jul 2022 16:57:55 +0200 Subject: [PATCH 107/240] Ported TimePicker to ControlTheme. And refactored shared date/time picker components into a separate file. --- .../Controls/DatePicker.xaml | 207 +++----- .../Controls/DateTimePickerShared.xaml | 116 +++++ .../Controls/FluentControls.xaml | 3 +- .../Controls/TimePicker.xaml | 465 ++++++++++++------ 4 files changed, 501 insertions(+), 290 deletions(-) create mode 100644 src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml diff --git a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml index 0537feb60b..0441415128 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml @@ -10,8 +10,21 @@ xmlns:sys="clr-namespace:System;assembly=netstandard" x:CompileBindings="True"> - - + + + + + + + + + + Error + + + + + @@ -27,105 +40,45 @@ 9,3,0,6 1 - - - - - - - - - - - - - - - - - - + - - - - + - - - + - - - - - - - - - - - - - - - - - - - - - - - + - + @@ -148,7 +101,9 @@ HorizontalAlignment="Stretch" VerticalAlignment="Top"/> - - diff --git a/src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml b/src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml new file mode 100644 index 0000000000..77271a149f --- /dev/null +++ b/src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml @@ -0,0 +1,116 @@ + + + + + + + Standard Item + Month Item + Button + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 58c33bf26f..262007082f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -59,7 +59,9 @@ + + @@ -72,6 +74,5 @@ - diff --git a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml index dc09032a36..b5f177b985 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml @@ -1,163 +1,320 @@ - - - - - 40 - 1 - 1 - 0,0,0,4 - 40 - 41 - 242 - 456 - 0,3,0,6 - 0,3,0,6 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + Error + + + + + + + + + 40 + 1 + 1 + 0,0,0,4 + 40 + 41 + 242 + 456 + 0,3,0,6 + 0,3,0,6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + - - - - - + + From 2dda677fc8099a484806be21574e1a5bbbf721b8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 10 Jul 2022 22:26:28 +0200 Subject: [PATCH 108/240] Ported SplitButton to ControlTheme. Not happy with the Tag hack but no better way to add additional states to a control currently. --- .../Controls/FluentControls.xaml | 2 +- .../Controls/SplitButton.xaml | 388 ++++++++---------- 2 files changed, 173 insertions(+), 217 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 262007082f..bc2352d5d0 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -39,6 +39,7 @@ + @@ -72,7 +73,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml b/src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml index fb0460d9a1..9a93b2625b 100644 --- a/src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml @@ -1,36 +1,120 @@ - - - + + Hello - + + + Hello + + + Disabled + + + Hello + + + Hello + - - 32 - 32 - 1 - 32 + 32 + 32 + 1 + 32 - - - - + + + - + + + + + + + + + + + + + + + + + + + + @@ -40,211 +124,83 @@ - - - - - - - - - - - @@ -147,9 +155,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml index 0441415128..bc47f3892e 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml @@ -280,10 +280,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml b/src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml index 77271a149f..be664b375d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml @@ -15,8 +15,8 @@ Standard Item Month Item Button - - + + @@ -90,6 +90,7 @@ + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/TimePicker.xaml b/src/Avalonia.Themes.Default/Controls/TimePicker.xaml index 0a5147e335..b0ffc97e68 100644 --- a/src/Avalonia.Themes.Default/Controls/TimePicker.xaml +++ b/src/Avalonia.Themes.Default/Controls/TimePicker.xaml @@ -1,175 +1,204 @@ - - - 40 - 1 - 1 - 0,0,0,4 - 40 - 41 - 242 - 456 - 0,3,0,6 - 0,3,0,6 - + + + + + + + + + + + + Error + + + + + + + - - - + 40 + 1 + 1 + 0,0,0,4 + 40 + 41 + 242 + 456 + 0,3,0,6 + 0,3,0,6 - + + + + + + + - - - + + + - - + + + - + + + + + - - - + + + - + + - - - - - + - + - + + - - - - - - - - + + + + diff --git a/src/Avalonia.Themes.Default/Controls/TitleBar.xaml b/src/Avalonia.Themes.Default/Controls/TitleBar.xaml index 7f8ed24076..a06dfbb834 100644 --- a/src/Avalonia.Themes.Default/Controls/TitleBar.xaml +++ b/src/Avalonia.Themes.Default/Controls/TitleBar.xaml @@ -1,53 +1,65 @@ - + - + - + - + - + - + - - - + - - + + + diff --git a/src/Avalonia.Themes.Default/Controls/ToggleButton.xaml b/src/Avalonia.Themes.Default/Controls/ToggleButton.xaml index 17fb2af16c..2cfbcf6b7a 100644 --- a/src/Avalonia.Themes.Default/Controls/ToggleButton.xaml +++ b/src/Avalonia.Themes.Default/Controls/ToggleButton.xaml @@ -1,40 +1,42 @@ - - - - - - - + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/ToggleSwitch.xaml b/src/Avalonia.Themes.Default/Controls/ToggleSwitch.xaml index 2c831cf360..d00358951a 100644 --- a/src/Avalonia.Themes.Default/Controls/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Default/Controls/ToggleSwitch.xaml @@ -1,69 +1,28 @@ - - - 0,0,0,6 - 6 - 6 - 0 - 0 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + + Classes="h1" /> + TextWrapping="Wrap" /> + Content="Enable automatic Updates?" + OffContent="Uit" + OnContent="Aan" + VerticalAlignment="Bottom" /> + Classes="h1" /> + TextWrapping="Wrap" /> @@ -71,29 +30,71 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/ToolTip.xaml b/src/Avalonia.Themes.Default/Controls/ToolTip.xaml index 35c1dceb8d..c4fddd8c4c 100644 --- a/src/Avalonia.Themes.Default/Controls/ToolTip.xaml +++ b/src/Avalonia.Themes.Default/Controls/ToolTip.xaml @@ -1,18 +1,22 @@ - + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/TransitioningContentControl.xaml b/src/Avalonia.Themes.Default/Controls/TransitioningContentControl.xaml index 6a4d56ccb7..63185e11e6 100644 --- a/src/Avalonia.Themes.Default/Controls/TransitioningContentControl.xaml +++ b/src/Avalonia.Themes.Default/Controls/TransitioningContentControl.xaml @@ -1,20 +1,21 @@ - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/TreeView.xaml b/src/Avalonia.Themes.Default/Controls/TreeView.xaml index 67a683dad9..2bc38a0673 100644 --- a/src/Avalonia.Themes.Default/Controls/TreeView.xaml +++ b/src/Avalonia.Themes.Default/Controls/TreeView.xaml @@ -1,27 +1,31 @@ - + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/TreeViewItem.xaml b/src/Avalonia.Themes.Default/Controls/TreeViewItem.xaml index 1996756001..7baf0ef2a6 100644 --- a/src/Avalonia.Themes.Default/Controls/TreeViewItem.xaml +++ b/src/Avalonia.Themes.Default/Controls/TreeViewItem.xaml @@ -1,93 +1,100 @@ - - + + - + + + + + + + + - + + + + + + + + + + + + + + + + + + - - - - - - - - + + diff --git a/src/Avalonia.Themes.Default/Controls/UserControl.xaml b/src/Avalonia.Themes.Default/Controls/UserControl.xaml index d6028daff8..56248f8c5f 100644 --- a/src/Avalonia.Themes.Default/Controls/UserControl.xaml +++ b/src/Avalonia.Themes.Default/Controls/UserControl.xaml @@ -1,16 +1,25 @@ - diff --git a/src/Avalonia.Themes.Default/Controls/Window.xaml b/src/Avalonia.Themes.Default/Controls/Window.xaml index 9c515ebe30..00b108343e 100644 --- a/src/Avalonia.Themes.Default/Controls/Window.xaml +++ b/src/Avalonia.Themes.Default/Controls/Window.xaml @@ -1,26 +1,33 @@ - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/Controls/WindowNotificationManager.xaml b/src/Avalonia.Themes.Default/Controls/WindowNotificationManager.xaml index 7c1efa2e82..6dacf63be5 100644 --- a/src/Avalonia.Themes.Default/Controls/WindowNotificationManager.xaml +++ b/src/Avalonia.Themes.Default/Controls/WindowNotificationManager.xaml @@ -1,45 +1,52 @@ - - + + + + + + + + + + + + + + + + + + + + - - - - - + + diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index f266402aef..8f5bea557c 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -1,70 +1,75 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 5b8cb5284bf81e1a846ea84a64828c6e7284732b Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 29 Jul 2022 02:59:43 -0400 Subject: [PATCH 180/240] Port default theme DataGrid to control themes --- .../Themes/Default.xaml | 652 ++++++++++-------- .../Themes/Fluent.xaml | 3 +- .../Diagnostics/Views/MainWindow.xaml | 5 +- 3 files changed, 364 insertions(+), 296 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml index 0d1fe43eb6..83d9332613 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml @@ -1,305 +1,377 @@ - - 4 - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml index a80cc2173c..5ae83427b5 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml @@ -57,8 +57,7 @@ diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml index c32638f6ca..680424122b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml @@ -11,10 +11,7 @@ - - + From ceb59fc475cd8774f03ef472c5becfa81ecfab90 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 29 Jul 2022 03:09:22 -0400 Subject: [PATCH 181/240] Workaround dev tools issue --- samples/ControlCatalog/App.xaml.cs | 9 ++++++--- .../Diagnostics/Views/MainWindow.xaml | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 7ebb87094a..a5535ea52d 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -78,9 +78,12 @@ namespace ControlCatalog public override void Initialize() { - Styles.Insert(0, Fluent); - Styles.Insert(1, ColorPickerFluent); - Styles.Insert(2, DataGridFluent); + Styles.Insert(0, DefaultLight); + Styles.Insert(1, ColorPickerDefault); + Styles.Insert(2, DataGridDefault); + //Styles.Insert(0, Fluent); + //Styles.Insert(1, ColorPickerFluent); + //Styles.Insert(2, DataGridFluent); AvaloniaXamlLoader.Load(this); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml index 680424122b..004518598c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml @@ -4,7 +4,8 @@ xmlns:diag="clr-namespace:Avalonia.Diagnostics" xmlns:default="using:Avalonia.Themes.Default" Title="Avalonia DevTools" - x:Class="Avalonia.Diagnostics.Views.MainWindow"> + x:Class="Avalonia.Diagnostics.Views.MainWindow" + Theme="{StaticResource {x:Type Window}}"> From 44b8df516c461dbbc38f825c5dab9aa01ee94dd2 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 29 Jul 2022 11:26:55 +0200 Subject: [PATCH 182/240] Set OverlayDismissEventPassThrough to false, add OverlayInputPassThroughElement property --- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 50 ++++++++++----------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index a0f3407b7a..bf938abc79 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -16,8 +16,8 @@ namespace Avalonia.Controls.Primitives /// Defines the property /// public static readonly DirectProperty IsOpenProperty = - AvaloniaProperty.RegisterDirect(nameof(IsOpen), - x => x.IsOpen); + AvaloniaProperty.RegisterDirect(nameof(IsOpen), + x => x.IsOpen); /// /// Defines the property @@ -39,16 +39,18 @@ namespace Avalonia.Controls.Primitives x => x.ShowMode, (x, v) => x.ShowMode = v); /// - /// Defines the AttachedFlyout property + /// Defines the property /// - public static readonly AttachedProperty AttachedFlyoutProperty = - AvaloniaProperty.RegisterAttached("AttachedFlyout", null); + public static readonly DirectProperty OverlayInputPassThroughElementProperty = + Popup.OverlayInputPassThroughElementProperty.AddOwner( + o => o._overlayInputPassThroughElement, + (o, v) => o._overlayInputPassThroughElement = v); /// - /// Defines the OverlayDismissEventPassThrough property + /// Defines the AttachedFlyout property /// - public static readonly StyledProperty OverlayDismissEventPassThroughProperty = - Popup.OverlayDismissEventPassThroughProperty.AddOwner(); + public static readonly AttachedProperty AttachedFlyoutProperty = + AvaloniaProperty.RegisterAttached("AttachedFlyout", null); private readonly Lazy _popupLazy; private bool _isOpen; @@ -58,10 +60,10 @@ namespace Avalonia.Controls.Primitives private PixelRect? _enlargePopupRectScreenPixelRect; private IDisposable? _transientDisposable; private Action? _popupHostChangedHandler; + private IInputElement? _overlayInputPassThroughElement; static FlyoutBase() { - OverlayDismissEventPassThroughProperty.OverrideDefaultValue(true); Control.ContextFlyoutProperty.Changed.Subscribe(OnContextFlyoutPropertyChanged); } @@ -109,25 +111,20 @@ namespace Avalonia.Controls.Primitives } /// - /// Gets or sets a value indicating whether the event that closes the flyout is passed - /// through to the parent window. + /// Gets or sets an element that should receive pointer input events even when underneath + /// the flyout's overlay. /// - /// - /// Clicks outside the the flyout cause the flyout to close. When is set to - /// false, these clicks will be handled by the flyout and not be registered by the parent - /// window. When set to true, the events will be passed through to the parent window. - /// - public bool OverlayDismissEventPassThrough + public IInputElement? OverlayInputPassThroughElement { - get => GetValue(OverlayDismissEventPassThroughProperty); - set => SetValue(OverlayDismissEventPassThroughProperty, value); + get => _overlayInputPassThroughElement; + set => SetAndRaise(OverlayInputPassThroughElementProperty, ref _overlayInputPassThroughElement, value); } IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host; - event Action? IPopupHostProvider.PopupHostChanged - { - add => _popupHostChangedHandler += value; + event Action? IPopupHostProvider.PopupHostChanged + { + add => _popupHostChangedHandler += value; remove => _popupHostChangedHandler -= value; } @@ -200,7 +197,7 @@ namespace Avalonia.Controls.Primitives Popup.OverlayInputPassThroughElement = null; ((ISetLogicalParent)Popup).SetParent(null); - + // Ensure this isn't active _transientDisposable?.Dispose(); _transientDisposable = null; @@ -255,8 +252,7 @@ namespace Avalonia.Controls.Primitives Popup.Child = CreatePresenter(); } - Popup.OverlayInputPassThroughElement = placementTarget; - Popup.OverlayDismissEventPassThrough = OverlayDismissEventPassThrough; + Popup.OverlayInputPassThroughElement = OverlayInputPassThroughElement; if (CancelOpening()) { @@ -386,7 +382,9 @@ namespace Avalonia.Controls.Primitives var popup = new Popup { WindowManagerAddShadowHint = false, - IsLightDismissEnabled = true + IsLightDismissEnabled = true, + //Note: This is required to prevent Button.Flyout from opening the flyout again after dismiss. + OverlayDismissEventPassThrough = false }; popup.Opened += OnPopupOpened; From 29d957282ece12d6f497027ed4fa9d28cfce33fd Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 29 Jul 2022 11:28:19 +0200 Subject: [PATCH 183/240] Remove unsetting --- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index bf938abc79..00ebcab70e 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -194,7 +194,6 @@ namespace Avalonia.Controls.Primitives IsOpen = false; Popup.IsOpen = false; - Popup.OverlayInputPassThroughElement = null; ((ISetLogicalParent)Popup).SetParent(null); From 81b0dce302f3e588ce2e6e5e16a9d1eb0b5d69ea Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 11:55:52 +0200 Subject: [PATCH 184/240] Fix RichTextBlock Inlines update handling --- samples/Sandbox/MainWindow.axaml | 15 ++++++ samples/Sandbox/MainWindow.axaml.cs | 46 ++++++++++++++++++- .../Media/TextFormatting/TextLayout.cs | 7 ++- .../Media/TextFormatting/TextLine.cs | 2 +- .../Media/TextFormatting/TextLineImpl.cs | 7 ++- src/Avalonia.Controls/RichTextBlock.cs | 22 ++++----- .../RichTextBlockTests.cs | 44 ++++++++++++++++++ .../Media/TextFormatting/TextLayoutTests.cs | 12 +++-- 8 files changed, 135 insertions(+), 20 deletions(-) diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index 0fc78795a6..cf2ce63c1f 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -9,6 +9,21 @@ + + + + + + diff --git a/samples/Sandbox/MainWindow.axaml.cs b/samples/Sandbox/MainWindow.axaml.cs index 7a8b56bbb6..3040385708 100644 --- a/samples/Sandbox/MainWindow.axaml.cs +++ b/samples/Sandbox/MainWindow.axaml.cs @@ -3,7 +3,9 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Documents; using Avalonia.Controls.Presenters; +using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.VisualTree; @@ -11,6 +13,8 @@ namespace Sandbox { public class MainWindow : Window { + private TestViewModel _dc; + public MainWindow() { this.InitializeComponent(); @@ -27,13 +31,30 @@ namespace Sandbox var textPresenter = e.NameScope.Find("PART_TextPresenter") as TextPresenter; - DataContext = new TestViewModel(textPresenter); + _dc = new TestViewModel(textPresenter); + + DataContext = _dc; } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } + + private void Button_OnClick(object? sender, RoutedEventArgs e) + { + _dc.InlineCollection = new InlineCollection + { + new Run(""), + new Run("test3") {FontWeight = Avalonia.Media.FontWeight.Bold}, + }; + // _dc.Text = "nununu"; + } + + private void TextButton_OnClick(object? sender, RoutedEventArgs e) + { + _dc.Text = "nununu"; + } } public class TestViewModel : ViewModelBase @@ -46,6 +67,29 @@ namespace Sandbox _textPresenter = textPresenter; } + private InlineCollection _inlineCollection; + private string _text; + + public string Text + { + get => _text; + set + { + _text = value; + RaisePropertyChanged(); + } + } + + public InlineCollection InlineCollection + { + get => _inlineCollection; + set + { + _inlineCollection = value; + RaisePropertyChanged(); + } + } + public double Distance { get => _distance; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index f3e8b5969c..0828b6518a 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -537,8 +537,13 @@ namespace Avalonia.Media.TextFormatting /// /// The collapsing width. /// The . - private TextCollapsingProperties GetCollapsingProperties(double width) + private TextCollapsingProperties? GetCollapsingProperties(double width) { + if(_textTrimming == TextTrimming.None) + { + return null; + } + return _textTrimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, _paragraphProperties.DefaultTextRunProperties)); } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLine.cs b/src/Avalonia.Base/Media/TextFormatting/TextLine.cs index c8a23097db..61b24dc8c5 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLine.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLine.cs @@ -153,7 +153,7 @@ namespace Avalonia.Media.TextFormatting /// /// A value that represents a collapsed line that can be displayed. /// - public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList); + public abstract TextLine Collapse(params TextCollapsingProperties?[] collapsingPropertiesList); /// /// Create a justified line based on justification text properties. diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 0ee791d935..a0aae76fad 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -119,7 +119,7 @@ namespace Avalonia.Media.TextFormatting } /// - public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList) + public override TextLine Collapse(params TextCollapsingProperties?[] collapsingPropertiesList) { if (collapsingPropertiesList.Length == 0) { @@ -128,6 +128,11 @@ namespace Avalonia.Media.TextFormatting var collapsingProperties = collapsingPropertiesList[0]; + if(collapsingProperties is null) + { + return this; + } + var collapsedRuns = collapsingProperties.Collapse(this); if (collapsedRuns is null) diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs index 0c8b1d125d..2f8bed3be7 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/RichTextBlock.cs @@ -44,8 +44,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty InlinesProperty = - AvaloniaProperty.Register( + public static readonly StyledProperty InlinesProperty = + AvaloniaProperty.Register( nameof(Inlines)); public static readonly DirectProperty CanCopyProperty = @@ -138,7 +138,7 @@ namespace Avalonia.Controls /// Gets or sets the inlines. /// [Content] - public InlineCollection Inlines + public InlineCollection? Inlines { get => GetValue(InlinesProperty); set => SetValue(InlinesProperty, value); @@ -159,7 +159,7 @@ namespace Avalonia.Controls remove => RemoveHandler(CopyingToClipboardEvent, value); } - internal bool HasComplexContent => Inlines.Count > 0; + internal bool HasComplexContent => Inlines != null && Inlines.Count > 0; /// /// Copies the current selection to the Clipboard. @@ -260,18 +260,18 @@ namespace Avalonia.Controls { if (!string.IsNullOrEmpty(_text)) { - Inlines.Add(_text); + Inlines?.Add(_text); _text = null; } - Inlines.Add(text); + Inlines?.Add(text); } } protected override string? GetText() { - return _text ?? Inlines.Text; + return _text ?? Inlines?.Text; } protected override void SetText(string? text) @@ -301,10 +301,10 @@ namespace Avalonia.Controls ITextSource textSource; - var inlines = Inlines; - if (HasComplexContent) { + var inlines = Inlines!; + var textRuns = new List(); foreach (var inline in inlines) @@ -537,7 +537,7 @@ namespace Avalonia.Controls switch (change.Property.Name) { - case nameof(InlinesProperty): + case nameof(Inlines): { OnInlinesChanged(change.OldValue as InlineCollection, change.NewValue as InlineCollection); InvalidateTextLayout(); @@ -553,7 +553,7 @@ namespace Avalonia.Controls return ""; } - var text = Inlines.Text ?? Text; + var text = GetText(); if (string.IsNullOrEmpty(text)) { diff --git a/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs index eb4b88956d..c74f13b808 100644 --- a/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs @@ -48,5 +48,49 @@ namespace Avalonia.Controls.UnitTests Assert.False(target.IsMeasureValid); } } + + [Fact] + public void Changing_Inlines_Should_Invalidate_Measure() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + var target = new RichTextBlock(); + + var inlines = new InlineCollection { new Run("Hello") }; + + target.Measure(Size.Infinity); + + Assert.True(target.IsMeasureValid); + + target.Inlines = inlines; + + Assert.False(target.IsMeasureValid); + } + } + + [Fact] + public void Changing_Inlines_Should_Reset_Inlines_Parent() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + var target = new RichTextBlock(); + + var run = new Run("Hello"); + + target.Inlines.Add(run); + + target.Measure(Size.Infinity); + + Assert.True(target.IsMeasureValid); + + target.Inlines = null; + + Assert.Null(run.Parent); + + target.Inlines = new InlineCollection { run }; + + Assert.Equal(target, run.Parent); + } + } } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 6d057d900e..c457a96299 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -993,9 +993,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var currentX = 0.0; - for (int j = 0; j < clusters.Count; j++) - { - var cluster = clusters[j]; + var cluster = text.Length; + + for (int j = 0; j < clusters.Count - 1; j++) + { + var glyphAdvance = glyphAdvances[j]; var characterHit = textLine.GetCharacterHitFromDistance(currentX); @@ -1005,9 +1007,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(currentX, distance); - var glyphAdvance = glyphAdvances[j]; - currentX += glyphAdvance; + + cluster = clusters[j]; } } } From e7be7cb88de6cbea64f8492b6899d53b4878030d Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 16:29:36 +0200 Subject: [PATCH 185/240] Fix that thing --- .../Media/TextFormatting/TextLineImpl.cs | 89 +++++++++++++------ 1 file changed, 61 insertions(+), 28 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index a0aae76fad..94095cf82b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -176,7 +176,7 @@ namespace Avalonia.Media.TextFormatting return GetRunCharacterHit(firstRun, FirstTextSourceIndex, 0); } - if (distance > WidthIncludingTrailingWhitespace) + if (distance >= WidthIncludingTrailingWhitespace) { var lastRun = _textRuns[_textRuns.Count - 1]; @@ -195,7 +195,7 @@ namespace Avalonia.Media.TextFormatting if(currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) { var rightToLeftIndex = i; - var rightToLeftDistance = shapedRun.Size.Width; + currentPosition += currentRun.TextSourceLength; while (rightToLeftIndex + 1 <= _textRuns.Count - 1) { @@ -206,19 +206,24 @@ namespace Avalonia.Media.TextFormatting break; } - rightToLeftIndex++; + currentPosition += nextShaped.TextSourceLength; - rightToLeftDistance += nextShaped.Size.Width; + rightToLeftIndex++; } - for (var j = rightToLeftIndex; rightToLeftIndex >= 0; j--) + for (var j = i; i <= rightToLeftIndex; j++) { + if(j > _textRuns.Count - 1) + { + break; + } + currentRun = _textRuns[j]; - if(distance <= currentDistance + rightToLeftDistance - currentRun.Size.Width) + if(currentDistance + currentRun.Size.Width <= distance) { - currentPosition += currentRun.TextSourceLength; - rightToLeftDistance -= currentRun.Size.Width; + currentDistance += currentRun.Size.Width; + currentPosition -= currentRun.TextSourceLength; continue; } @@ -255,14 +260,18 @@ namespace Avalonia.Media.TextFormatting { characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); - var offset = Math.Max(0, currentPosition - shapedRun.Text.Start); + //var offset = 0; - if (!shapedRun.GlyphRun.IsLeftToRight) - { - offset = Math.Max(0, offset - shapedRun.Text.End); - } + //if (shapedRun.GlyphRun.IsLeftToRight) + //{ + // offset = Math.Max(0, currentPosition - shapedRun.Text.Start); + //} + //else + //{ + // offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length); + //} - characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); + //characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); break; } @@ -303,35 +312,50 @@ namespace Avalonia.Media.TextFormatting { var i = index; + var rightToLeftWidth = currentRun.Size.Width; + while (i + 1 <= _textRuns.Count - 1) { var nextRun = _textRuns[i + 1]; - if (nextRun is ShapedTextCharacters nextShapedRun) + if (nextRun is ShapedTextCharacters nextShapedRun && !nextShapedRun.ShapedBuffer.IsLeftToRight) { - if (nextShapedRun.ShapedBuffer.IsLeftToRight) - { - break; - } - } + i++; + + rightToLeftWidth += nextRun.Size.Width; - i++; + continue; + } + + break; } - while (i > index) + if(i > index) { - var rightToLeftRun = _textRuns[i]; + while (i >= index) + { + currentRun = _textRuns[i]; + + rightToLeftWidth -= currentRun.Size.Width; - currentPosition += rightToLeftRun.TextSourceLength; + if (currentPosition + currentRun.TextSourceLength >= characterIndex) + { + break; + } - remainingLength -= rightToLeftRun.TextSourceLength; + currentPosition += currentRun.TextSourceLength; - i--; + remainingLength -= currentRun.TextSourceLength; + + i--; + } + + currentDistance += rightToLeftWidth; } } - if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, - flowDirection, out var distance, out _)) + if (currentPosition + currentRun.TextSourceLength >= characterIndex && + TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, flowDirection, out var distance, out _)) { return currentDistance + distance; } @@ -608,6 +632,15 @@ namespace Avalonia.Media.TextFormatting } else { + if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex) + { + startX += currentRun.Size.Width; + + currentPosition += currentRun.TextSourceLength; + + continue; + } + if (currentPosition < firstTextSourceIndex) { startX += currentRun.Size.Width; From d524dc9cdbc498ff2c8a468fe7cdcdc125ce37c9 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 16:56:43 +0200 Subject: [PATCH 186/240] Only apply the font fallback to regions of missing glyphs --- samples/Sandbox/MainWindow.axaml | 26 +--- samples/Sandbox/MainWindow.axaml.cs | 115 +----------------- .../Media/TextFormatting/TextCharacters.cs | 26 ++-- .../Presenters/TextPresenter.cs | 4 +- src/Avalonia.Controls/TextBox.cs | 2 - 5 files changed, 18 insertions(+), 155 deletions(-) diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index cf2ce63c1f..d429777eeb 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,29 +1,5 @@ - - - - - - - - - - - - - - - - + diff --git a/samples/Sandbox/MainWindow.axaml.cs b/samples/Sandbox/MainWindow.axaml.cs index 3040385708..2d36ed6d28 100644 --- a/samples/Sandbox/MainWindow.axaml.cs +++ b/samples/Sandbox/MainWindow.axaml.cs @@ -13,126 +13,15 @@ namespace Sandbox { public class MainWindow : Window { - private TestViewModel _dc; - public MainWindow() { this.InitializeComponent(); - this.AttachDevTools(); - - var textBox = this.FindControl("txtBox"); - - textBox.TemplateApplied += TextBox_TemplateApplied; - } - - private void TextBox_TemplateApplied(object sender, Avalonia.Controls.Primitives.TemplateAppliedEventArgs e) - { - var textBox = sender as TextBox; - - var textPresenter = e.NameScope.Find("PART_TextPresenter") as TextPresenter; - - _dc = new TestViewModel(textPresenter); - - DataContext = _dc; + this.AttachDevTools(); } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } - - private void Button_OnClick(object? sender, RoutedEventArgs e) - { - _dc.InlineCollection = new InlineCollection - { - new Run(""), - new Run("test3") {FontWeight = Avalonia.Media.FontWeight.Bold}, - }; - // _dc.Text = "nununu"; - } - - private void TextButton_OnClick(object? sender, RoutedEventArgs e) - { - _dc.Text = "nununu"; - } - } - - public class TestViewModel : ViewModelBase - { - private readonly TextPresenter _textPresenter; - private double _distance = 45; - - public TestViewModel(TextPresenter textPresenter) - { - _textPresenter = textPresenter; - } - - private InlineCollection _inlineCollection; - private string _text; - - public string Text - { - get => _text; - set - { - _text = value; - RaisePropertyChanged(); - } - } - - public InlineCollection InlineCollection - { - get => _inlineCollection; - set - { - _inlineCollection = value; - RaisePropertyChanged(); - } - } - - public double Distance - { - get => _distance; - set - { - OnDistanceChanged(value); - RaisePropertyChanged(); - } - } - - private void OnDistanceChanged(double distance) - { - if(distance < 0) - { - distance = 0; - } - - if(distance > _textPresenter.TextLayout.Bounds.Width) - { - distance = _textPresenter.TextLayout.Bounds.Width; - } - - var height = _textPresenter.TextLayout.Bounds.Height; - - var distanceY = height / 2; - - _textPresenter.MoveCaretToPoint(new Point(distance, distanceY)); - - var caretIndex = _textPresenter.CaretIndex; - - Debug.WriteLine(caretIndex); - - _distance = distance; - } - } - - public class ViewModelBase : INotifyPropertyChanged - { - public event PropertyChangedEventHandler PropertyChanged; - - protected void RaisePropertyChanged([CallerMemberName]string propertyName = "") - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - } + } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index ab72601c3e..6611357ee7 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -38,7 +38,7 @@ namespace Avalonia.Media.TextFormatting /// Gets a list of . /// /// The shapeable text characters. - internal IReadOnlyList GetShapeableCharacters(ReadOnlySlice runText, sbyte biDiLevel, + internal IReadOnlyList GetShapeableCharacters(ReadOnlySlice runText, sbyte biDiLevel, ref TextRunProperties? previousProperties) { var shapeableCharacters = new List(2); @@ -65,7 +65,7 @@ namespace Avalonia.Media.TextFormatting /// The bidi level of the run. /// /// A list of shapeable text runs. - private static ShapeableTextCharacters CreateShapeableRun(ReadOnlySlice text, + private static ShapeableTextCharacters CreateShapeableRun(ReadOnlySlice text, TextRunProperties defaultProperties, sbyte biDiLevel, ref TextRunProperties? previousProperties) { var defaultTypeface = defaultProperties.Typeface; @@ -76,7 +76,7 @@ namespace Avalonia.Media.TextFormatting { if (script == Script.Common && previousTypeface is not null) { - if(TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out var fallbackCount, out _)) + if (TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out var fallbackCount, out _)) { return new ShapeableTextCharacters(text.Take(fallbackCount), defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel); @@ -86,10 +86,10 @@ namespace Avalonia.Media.TextFormatting return new ShapeableTextCharacters(text.Take(count), defaultProperties.WithTypeface(currentTypeface), biDiLevel); } - + if (previousTypeface is not null) { - if(TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out count, out _)) + if (TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out count, out _)) { return new ShapeableTextCharacters(text.Take(count), defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel); @@ -106,12 +106,12 @@ namespace Avalonia.Media.TextFormatting { continue; } - + codepoint = codepointEnumerator.Current; - + break; } - + //ToDo: Fix FontFamily fallback var matchFound = FontManager.Current.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, @@ -157,14 +157,14 @@ namespace Avalonia.Media.TextFormatting /// /// protected static bool TryGetShapeableLength( - ReadOnlySlice text, - Typeface typeface, + ReadOnlySlice text, + Typeface typeface, Typeface? defaultTypeface, out int length, out Script script) { length = 0; - script = Script.Unknown; + script = Script.Unknown; if (text.Length == 0) { @@ -182,7 +182,7 @@ namespace Avalonia.Media.TextFormatting var currentScript = currentGrapheme.FirstCodepoint.Script; - if (currentScript != Script.Common && defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) + if (defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) { break; } @@ -192,7 +192,7 @@ namespace Avalonia.Media.TextFormatting { break; } - + if (currentScript != script) { if (script is Script.Unknown || currentScript != Script.Common && diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 4f9be86641..e540f58195 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -498,13 +498,13 @@ namespace Avalonia.Controls.Presenters IReadOnlyList>? textStyleOverrides = null; - if (length > 0) + if (length > 0 && SelectionForegroundBrush != null) { textStyleOverrides = new[] { new ValueSpan(start, length, new GenericTextRunProperties(typeface, FontSize, - foregroundBrush: SelectionForegroundBrush ?? Brushes.Red)) + foregroundBrush: SelectionForegroundBrush)) }; } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 8490bfd3a0..1773163acc 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1245,8 +1245,6 @@ namespace Avalonia.Controls var caretIndex = hit.TextPosition; - Debug.WriteLine($"TextPos: {caretIndex}, X: {point.X}"); - var text = Text; if (text != null && _wordSelectionStart >= 0) From ca84dda76b168c0181819df735df3bfa1512b924 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 16:59:46 +0200 Subject: [PATCH 187/240] Revert changes --- samples/Sandbox/MainWindow.axaml | 1 - samples/Sandbox/MainWindow.axaml.cs | 12 +++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index d429777eeb..6929f192c7 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,5 +1,4 @@ - diff --git a/samples/Sandbox/MainWindow.axaml.cs b/samples/Sandbox/MainWindow.axaml.cs index 2d36ed6d28..3d54036d29 100644 --- a/samples/Sandbox/MainWindow.axaml.cs +++ b/samples/Sandbox/MainWindow.axaml.cs @@ -1,13 +1,7 @@ -using System.ComponentModel; -using System.Diagnostics; -using System.Runtime.CompilerServices; using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Documents; -using Avalonia.Controls.Presenters; -using Avalonia.Interactivity; using Avalonia.Markup.Xaml; -using Avalonia.VisualTree; +using Avalonia.Win32.WinRT.Composition; namespace Sandbox { @@ -16,12 +10,12 @@ namespace Sandbox public MainWindow() { this.InitializeComponent(); - this.AttachDevTools(); + this.AttachDevTools(); } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } - } + } } From 6fefe5c8031a2aa24050c477535dff5297ca2f38 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 17:27:39 +0200 Subject: [PATCH 188/240] Align justified text to the natural start --- .../TextFormatting/InterWordJustification.cs | 17 ++++++++++++----- .../Media/TextFormatting/TextLineImpl.cs | 5 +++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs index df83ada34a..a49e4ef13b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs +++ b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs @@ -15,6 +15,13 @@ namespace Avalonia.Media.TextFormatting public override void Justify(TextLine textLine) { + var lineImpl = textLine as TextLineImpl; + + if(lineImpl is null) + { + return; + } + var paragraphWidth = Width; if (double.IsInfinity(paragraphWidth)) @@ -22,12 +29,12 @@ namespace Avalonia.Media.TextFormatting return; } - if (textLine.NewLineLength > 0) + if (lineImpl.NewLineLength > 0) { return; } - var textLineBreak = textLine.TextLineBreak; + var textLineBreak = lineImpl.TextLineBreak; if (textLineBreak is not null && textLineBreak.TextEndOfLine is not null) { @@ -39,7 +46,7 @@ namespace Avalonia.Media.TextFormatting var breakOportunities = new Queue(); - foreach (var textRun in textLine.TextRuns) + foreach (var textRun in lineImpl.TextRuns) { var text = textRun.Text; @@ -68,10 +75,10 @@ namespace Avalonia.Media.TextFormatting return; } - var remainingSpace = Math.Max(0, paragraphWidth - textLine.WidthIncludingTrailingWhitespace); + var remainingSpace = Math.Max(0, paragraphWidth - lineImpl.WidthIncludingTrailingWhitespace); var spacing = remainingSpace / breakOportunities.Count; - foreach (var textRun in textLine.TextRuns) + foreach (var textRun in lineImpl.TextRuns) { var text = textRun.Text; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 94095cf82b..2c4e553355 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -1536,6 +1536,11 @@ namespace Avalonia.Media.TextFormatting var textAlignment = _paragraphProperties.TextAlignment; var paragraphFlowDirection = _paragraphProperties.FlowDirection; + if(textAlignment == TextAlignment.Justify) + { + textAlignment = TextAlignment.Start; + } + switch (textAlignment) { case TextAlignment.Start: From 2cf660b601c69ad28656dfb36de0be96c5a4783b Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 19:53:44 +0200 Subject: [PATCH 189/240] Second attempt to fix the font fallback --- src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index 6611357ee7..42a9e61c36 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -182,7 +182,7 @@ namespace Avalonia.Media.TextFormatting var currentScript = currentGrapheme.FirstCodepoint.Script; - if (defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) + if (!currentGrapheme.FirstCodepoint.IsWhiteSpace && defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) { break; } From d5084eaf7582b83630c7a7b1c90b88f17503ce8e Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 20:14:48 +0200 Subject: [PATCH 190/240] Fix GetCharacterHitFromDistance --- .../Media/TextFormatting/TextLineImpl.cs | 24 +++++++++---------- src/Avalonia.Controls/RichTextBlock.cs | 2 +- src/Avalonia.Controls/TextBox.cs | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 2c4e553355..b9147c0f65 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -260,18 +260,18 @@ namespace Avalonia.Media.TextFormatting { characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); - //var offset = 0; - - //if (shapedRun.GlyphRun.IsLeftToRight) - //{ - // offset = Math.Max(0, currentPosition - shapedRun.Text.Start); - //} - //else - //{ - // offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length); - //} - - //characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); + var offset = 0; + + if (shapedRun.GlyphRun.IsLeftToRight) + { + offset = Math.Max(0, currentPosition - shapedRun.Text.Start); + } + else + { + offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length); + } + + characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); break; } diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs index 2f8bed3be7..1f8abbc30d 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/RichTextBlock.cs @@ -276,7 +276,7 @@ namespace Avalonia.Controls protected override void SetText(string? text) { - var oldValue = _text ?? Inlines?.Text; + var oldValue = GetText(); AddText(text); diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 1773163acc..4c9e9327d4 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1241,9 +1241,9 @@ namespace Avalonia.Controls MathUtilities.Clamp(point.X, 0, Math.Max(_presenter.Bounds.Width - 1, 0)), MathUtilities.Clamp(point.Y, 0, Math.Max(_presenter.Bounds.Height - 1, 0))); - var hit = _presenter.TextLayout.HitTestPoint(point); + _presenter.MoveCaretToPoint(point); - var caretIndex = hit.TextPosition; + var caretIndex = _presenter.CaretIndex; var text = Text; From adc81678ca6049def8124965642b3ad90b34e462 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 29 Jul 2022 21:56:27 +0200 Subject: [PATCH 191/240] Only add offsets for LeftToRight runs --- src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index b9147c0f65..6c6939d2a0 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -266,10 +266,10 @@ namespace Avalonia.Media.TextFormatting { offset = Math.Max(0, currentPosition - shapedRun.Text.Start); } - else - { - offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length); - } + //else + //{ + // offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length); + //} characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); From c94120c065a69838399f87814b8bbcc0340e5789 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Sat, 30 Jul 2022 09:53:07 +0200 Subject: [PATCH 192/240] Try to fix tests on mac --- .../Media/TextFormatting/TextLayoutTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index c457a96299..43948e9229 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -1005,7 +1005,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(cluster)); - Assert.Equal(currentX, distance); + Assert.Equal(currentX, distance, 5); currentX += glyphAdvance; From 2a5760921761d819264d190a381a2314b09a0516 Mon Sep 17 00:00:00 2001 From: Evgeny Gorbovoy Date: Sat, 30 Jul 2022 17:16:16 +0200 Subject: [PATCH 193/240] + more `ContainsExclusive` for nodes --- .../Rendering/SceneGraph/ExperimentalAcrylicNode.cs | 2 +- src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs | 2 +- src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs index 59ebcf5109..12b67105e9 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs @@ -83,7 +83,7 @@ namespace Avalonia.Rendering.SceneGraph if (Material != null) { var rect = Rect.Rect; - return rect.Contains(p); + return rect.ContainsExclusive(p); } } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs index 9199611ed6..1f58111ecf 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs @@ -73,6 +73,6 @@ namespace Avalonia.Rendering.SceneGraph } /// - public override bool HitTest(Point p) => Bounds.Contains(p); + public override bool HitTest(Point p) => Bounds.ContainsExclusive(p); } } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs index 23267166a5..339881e675 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs @@ -109,7 +109,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public override bool HitTest(Point p) => Bounds.Contains(p); + public override bool HitTest(Point p) => Bounds.ContainsExclusive(p); public override void Dispose() { From 86265f49aa8c9b13d2e420f505a65ad9df16f5b2 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 30 Jul 2022 16:03:48 -0400 Subject: [PATCH 194/240] Update samples/ControlCatalog/App.xaml.cs --- samples/ControlCatalog/App.xaml.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index a5535ea52d..7ebb87094a 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -78,12 +78,9 @@ namespace ControlCatalog public override void Initialize() { - Styles.Insert(0, DefaultLight); - Styles.Insert(1, ColorPickerDefault); - Styles.Insert(2, DataGridDefault); - //Styles.Insert(0, Fluent); - //Styles.Insert(1, ColorPickerFluent); - //Styles.Insert(2, DataGridFluent); + Styles.Insert(0, Fluent); + Styles.Insert(1, ColorPickerFluent); + Styles.Insert(2, DataGridFluent); AvaloniaXamlLoader.Load(this); } From 33931e99bb9222339b50994b514f960d4b3c34b2 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 30 Jul 2022 23:57:00 -0400 Subject: [PATCH 195/240] Fix unit tests --- tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 0bb6c01041..29148e6f2e 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -466,7 +466,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var xaml = @" - + "; var styles = AvaloniaRuntimeXamlLoader.Parse(xaml); From 195ef79e0a3151007eaa3d3b64a8a79d269ee915 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 31 Jul 2022 00:19:34 -0400 Subject: [PATCH 196/240] Convert ColorPicker default styles to ControlThemes --- .../Themes/Default/ColorPreviewer.xaml | 23 +- .../Themes/Default/ColorSlider.xaml | 346 +++++++++--------- .../Themes/Default/ColorSpectrum.xaml | 94 ++--- .../Themes/Default/Default.xaml | 76 ++-- 4 files changed, 274 insertions(+), 265 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml index c3bc7df4a4..e067fe4ab5 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml @@ -1,15 +1,14 @@ - + - - - 80 - 40 - + + 80 + 40 - + - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml index 35cd7a9faa..9aa2dcd9f9 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml @@ -1,188 +1,190 @@ - + - + - + - + - - + + + + + + - - - - + + - - + - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml index 0e57f6b483..0e137c89c6 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml @@ -1,9 +1,10 @@ - + - - - - + + + - - - + + + - - - - - + + + + + - + - - - + + + - + + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml index db1fa3ee4e..b452e34394 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml @@ -3,42 +3,48 @@ xmlns:converters="using:Avalonia.Controls.Converters"> - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - + - - - + + + + + + + + + + + From bdd451cdf96179dc9c28659ee21db3cec8b8ee0b Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 31 Jul 2022 00:39:45 -0400 Subject: [PATCH 197/240] Adjust ColorPicker tab background corner radius --- .../Themes/Fluent/ColorPicker.xaml | 15 ++++----------- .../Themes/Fluent/ColorView.xaml | 8 ++++++++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index 907b00dfff..50c5c06479 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -3,9 +3,6 @@ xmlns:controls="using:Avalonia.Controls" x:CompileBindings="True"> - - 5,5,0,0 - @@ -25,7 +22,7 @@ Padding="0,0,10,0" UseLayoutRounding="False"> - @@ -45,8 +42,10 @@ - + - - - - diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 993745b1e5..153b4422ea 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -77,6 +77,8 @@ 17.7761 14 17.5 14H9.94999ZM7.5 16C6.67157 16 6 15.3284 6 14.5C6 13.6716 6.67157 13 7.5 13C8.32843 13 9 13.6716 9 14.5C9 15.3284 8.32843 16 7.5 16Z + + 5,5,0,0 @@ -644,6 +646,12 @@ + + + + From b3bd2e54ecb5fe274164f9ddd96be7482b4a1173 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Mon, 1 Aug 2022 16:19:41 +0300 Subject: [PATCH 198/240] Use correct ToggleModifier in ListBox multiselection on MacOS. --- src/Avalonia.Controls/ListBox.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index 79285bb86b..80b5259a53 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Selection; using Avalonia.Controls.Templates; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.VisualTree; namespace Avalonia.Controls @@ -157,7 +158,7 @@ namespace Avalonia.Controls e.Source, true, e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), - e.KeyModifiers.HasAllFlags(KeyModifiers.Control), + e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService().CommandModifiers), point.Properties.IsRightButtonPressed); } } From 9f031603424cbcd7b1f4f5bcc5ce38c0c2b93152 Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Mon, 1 Aug 2022 18:27:15 +0300 Subject: [PATCH 199/240] Update MacOS vmImage because current one is deprecated https://github.com/actions/virtual-environments/issues/5583 --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index edf3c3d819..dde1da1446 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -59,7 +59,7 @@ jobs: variables: SolutionDir: '$(Build.SourcesDirectory)' pool: - vmImage: 'macOS-10.15' + vmImage: 'macos-12' steps: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 3.1.418' From 8d3984b929e21e44cda15c420d5818ff925dbd4e Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Mon, 1 Aug 2022 18:31:34 +0300 Subject: [PATCH 200/240] macos-12 is in beta,use macos-11 instead --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index dde1da1446..78012b9041 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -59,7 +59,7 @@ jobs: variables: SolutionDir: '$(Build.SourcesDirectory)' pool: - vmImage: 'macos-12' + vmImage: 'macos-11' steps: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 3.1.418' From 326fffc9fe14aafb571abc794331fe357423df7b Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Mon, 1 Aug 2022 18:45:11 +0300 Subject: [PATCH 201/240] WIP --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 78012b9041..1d2fe88341 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -59,7 +59,7 @@ jobs: variables: SolutionDir: '$(Build.SourcesDirectory)' pool: - vmImage: 'macos-11' + vmImage: 'macos-12' steps: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 3.1.418' @@ -91,7 +91,7 @@ jobs: inputs: actions: 'build' scheme: '' - sdk: 'macosx11.1' + sdk: 'macosx13.1' configuration: 'Release' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' xcodeVersion: '12' # Options: 8, 9, default, specifyPath From a62c7936690d28cb8a42ac2304b5a30de50b1afc Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Mon, 1 Aug 2022 18:52:59 +0300 Subject: [PATCH 202/240] WIP --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1d2fe88341..2a6e1b5475 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -59,7 +59,7 @@ jobs: variables: SolutionDir: '$(Build.SourcesDirectory)' pool: - vmImage: 'macos-12' + vmImage: 'macos-11' steps: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 3.1.418' @@ -91,7 +91,7 @@ jobs: inputs: actions: 'build' scheme: '' - sdk: 'macosx13.1' + sdk: 'macosx12.2' configuration: 'Release' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' xcodeVersion: '12' # Options: 8, 9, default, specifyPath From ebb8ffed1969c5d9156b6affa381a0ebf8c55e4f Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Mon, 1 Aug 2022 19:10:25 +0300 Subject: [PATCH 203/240] WIP --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2a6e1b5475..7e4ba64fb9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -59,7 +59,7 @@ jobs: variables: SolutionDir: '$(Build.SourcesDirectory)' pool: - vmImage: 'macos-11' + vmImage: 'macos-12' steps: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 3.1.418' @@ -91,10 +91,10 @@ jobs: inputs: actions: 'build' scheme: '' - sdk: 'macosx12.2' + sdk: 'macosx12.3' configuration: 'Release' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' - xcodeVersion: '12' # Options: 8, 9, default, specifyPath + xcodeVersion: '13.3' # Options: 8, 9, default, specifyPath args: '-derivedDataPath ./' - task: CmdLine@2 From 3d845206cd8d6c7a4395000b0892015354e0bc99 Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Mon, 1 Aug 2022 19:31:40 +0300 Subject: [PATCH 204/240] WIP --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7e4ba64fb9..52fc8db53c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -94,7 +94,7 @@ jobs: sdk: 'macosx12.3' configuration: 'Release' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' - xcodeVersion: '13.3' # Options: 8, 9, default, specifyPath + xcodeVersion: '13' # Options: 8, 9, default, specifyPath args: '-derivedDataPath ./' - task: CmdLine@2 From f87052311bd6e3448a198000fd1fe1915c5ab7c6 Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 1 Aug 2022 22:01:38 -0400 Subject: [PATCH 205/240] Use resources to adjust tab background corner radius in ColorPicker --- .../Themes/Fluent/ColorPicker.xaml | 8 ++++++-- .../Themes/Fluent/ColorView.xaml | 12 +++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index 50c5c06479..797d6c90d7 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -45,7 +45,6 @@ + SelectedIndex="{Binding SelectedIndex, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"> + + + 5,5,0,0 + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 153b4422ea..59cc48975f 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -77,8 +77,8 @@ 17.7761 14 17.5 14H9.94999ZM7.5 16C6.67157 16 6 15.3284 6 14.5C6 13.6716 6.67157 13 7.5 13C8.32843 13 9 13.6716 9 14.5C9 15.3284 8.32843 16 7.5 16Z - - 5,5,0,0 + + 3 @@ -99,7 +99,7 @@ HorizontalAlignment="Stretch" VerticalAlignment="Top" Background="{DynamicResource SystemControlBackgroundBaseLowBrush}" - CornerRadius="{TemplateBinding CornerRadius}" /> + CornerRadius="{DynamicResource ColorViewTabBackgroundCornerRadius}" /> - - - - From 53e7a741c9a476e83a4a0c89956715bac8cf42cc Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 1 Aug 2022 22:01:55 -0400 Subject: [PATCH 206/240] Switch back to default flyout placement in ColorPicker --- .../Themes/Fluent/ColorPicker.xaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index 797d6c90d7..74a1df4991 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -42,8 +42,7 @@ - + Date: Tue, 2 Aug 2022 11:48:20 +0200 Subject: [PATCH 207/240] fix: XML Comment --- src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs | 2 +- .../Rendering/Composition/Animations/CompositionAnimation.cs | 4 ++-- .../Rendering/Composition/Animations/ExpressionAnimation.cs | 2 +- .../Rendering/Composition/Animations/KeyFrameAnimation.cs | 4 ++-- src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs | 2 +- src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs | 2 +- src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs | 2 +- src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs | 2 +- src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs | 2 +- src/Avalonia.Base/Utilities/MathUtilities.cs | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs index 56a90f31ea..ab17263806 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs @@ -224,7 +224,7 @@ namespace Avalonia.Media.TextFormatting.Unicode } /// - /// Returns if is between + /// Returns if is between /// and , inclusive. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs index 19d316eb85..23f2aeaf90 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs @@ -16,10 +16,10 @@ namespace Avalonia.Rendering.Composition.Animations /// This is the base class for ExpressionAnimation and KeyFrameAnimation. /// /// - /// Use the method to start the animation. + /// Use the method to start the animation. /// Value parameters (as opposed to reference parameters which are set using ) /// are copied and "embedded" into an expression at the time CompositionObject.StartAnimation is called. - /// Changing the value of the variable after is called will not affect + /// Changing the value of the variable after is called will not affect /// the value of the ExpressionAnimation. /// See the remarks section of ExpressionAnimation for additional information. /// diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs index 577910d975..ec2972044e 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs @@ -16,7 +16,7 @@ namespace Avalonia.Rendering.Composition.Animations /// This contrasts s, which use an interpolator to define how the animating /// property changes over time. The mathematical equation can be defined using references to properties /// of Composition objects, mathematical functions and operators and Input. - /// Use the method to start the animation. + /// Use the method to start the animation. /// public class ExpressionAnimation : CompositionAnimation { diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs index 4692fde5e3..d21a4d06e3 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs @@ -24,9 +24,9 @@ namespace Avalonia.Rendering.Composition.Animations /// The delay behavior of the key frame animation. /// public AnimationDelayBehavior DelayBehavior { get; set; } - + /// - /// Delay before the animation starts after is called. + /// Delay before the animation starts after is called. /// public System.TimeSpan DelayTime { get; set; } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs index 4b43f93aee..e1d43b4268 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The fill brush. /// The stroke pen. /// The geometry. - /// Child scenes for drawing visual brushes. + /// Auxiliary data required to draw the brush. public GeometryNode(Matrix transform, IBrush? brush, IPen? pen, diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs index 1f58111ecf..b23b0684d3 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The transform. /// The foreground brush. /// The glyph run to draw. - /// Child scenes for drawing visual brushes. + /// Auxiliary data required to draw the brush. public GlyphRunNode( Matrix transform, IBrush foreground, diff --git a/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs index ee5ec0a5fc..b027e43378 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The stroke pen. /// The start point of the line. /// The end point of the line. - /// Child scenes for drawing visual brushes. + /// Auxiliary data required to draw the brush. public LineNode( Matrix transform, IPen pen, diff --git a/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs index 549c1fd7de..5fd200ddff 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs @@ -17,7 +17,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The opacity mask to push. /// The bounds of the mask. - /// Child scenes for drawing visual brushes. + /// Auxiliary data required to draw the brush. public OpacityMaskNode(IBrush mask, Rect bounds, IDisposable? aux = null) : base(Rect.Empty, Matrix.Identity, aux) { diff --git a/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs index a9d1bf96e5..f2ffd7411c 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs @@ -20,7 +20,7 @@ namespace Avalonia.Rendering.SceneGraph /// The stroke pen. /// The rectangle to draw. /// The box shadow parameters - /// Child scenes for drawing visual brushes. + /// Auxiliary data required to draw the brush. public RectangleNode( Matrix transform, IBrush? brush, diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 3d5be806e1..d381979c1e 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -255,7 +255,7 @@ namespace Avalonia.Utilities /// /// Clamps a value between a minimum and maximum value. /// - /// The value. + /// The value. /// The minimum value. /// The maximum value. /// The clamped value. From 89c0ef56371bddf68590f8f21efe61672d1084fc Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 2 Aug 2022 12:02:04 +0200 Subject: [PATCH 208/240] refactoring: removed some unused namespace --- .../Rendering/Composition/Animations/CompositionAnimation.cs | 3 --- src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs | 2 -- src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs | 4 ---- src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs | 2 -- src/Skia/Avalonia.Skia/ImmutableBitmap.cs | 3 --- src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs | 2 -- 6 files changed, 16 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs index 19d316eb85..5499e1119a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs @@ -1,12 +1,9 @@ // ReSharper disable InconsistentNaming // ReSharper disable CheckNamespace -using System; -using System.Collections.Generic; using System.Numerics; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; -using Avalonia.Rendering.Composition.Transport; // Special license applies License.md diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs index 4b43f93aee..747e96f130 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Generic; using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Platform; -using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph { diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs index 1f58111ecf..ed45e54f57 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs @@ -1,10 +1,6 @@ using System; -using System.Collections.Generic; - using Avalonia.Media; -using Avalonia.Media.Immutable; using Avalonia.Platform; -using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph { diff --git a/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs index ee5ec0a5fc..973b28dead 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Generic; using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Platform; -using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph { diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index a80f406989..6400d67fde 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -1,11 +1,8 @@ using System; using System.IO; -using System.Runtime.CompilerServices; -using System.Security.Cryptography; using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Skia.Helpers; -using Avalonia.Media.Imaging; using SkiaSharp; namespace Avalonia.Skia diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 7f1af46e97..81fa8c4bce 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using Avalonia.Controls; @@ -9,7 +8,6 @@ using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Platform; -using Avalonia.Media.Imaging; using SharpDX.DirectWrite; using GlyphRun = Avalonia.Media.GlyphRun; using TextAlignment = Avalonia.Media.TextAlignment; From bf9a0d8656d787fc67a7ba8aea3f9c6097624f0c Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 2 Aug 2022 13:17:01 +0200 Subject: [PATCH 209/240] Use a different arabic font for tests --- .../Assets/NotoKufiArabic-Regular.ttf | Bin 122736 -> 0 bytes .../Assets/NotoSansArabic-Regular.ttf | Bin 0 -> 177004 bytes .../Media/CustomFontManagerImpl.cs | 8 +++++++- .../Media/TextFormatting/TextLayoutTests.cs | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) delete mode 100644 tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf create mode 100644 tests/Avalonia.RenderTests/Assets/NotoSansArabic-Regular.ttf diff --git a/tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf b/tests/Avalonia.RenderTests/Assets/NotoKufiArabic-Regular.ttf deleted file mode 100644 index 6d2ad86f947a530de5dec09b9a230948d0f512c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122736 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 V75=m6xC;lbHm=3_}(L8AakEU|`p@iY%Zk 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 z�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 literal 0 HcmV?d00001 diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index 13cc14b03e..7d0cf05b0b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs @@ -16,7 +16,7 @@ namespace Avalonia.Skia.UnitTests.Media private readonly Typeface _defaultTypeface = new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono"); private readonly Typeface _arabicTypeface = - new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Kufi Arabic"); + new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Sans Arabic"); private readonly Typeface _italicTypeface = new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Sans", FontStyle.Italic); private readonly Typeface _emojiTypeface = @@ -82,6 +82,12 @@ namespace Avalonia.Skia.UnitTests.Media skTypeface = typefaceCollection.Get(typeface); break; } + case "Noto Sans Arabic": + { + var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_arabicTypeface.FontFamily); + skTypeface = typefaceCollection.Get(typeface); + break; + } case FontFamily.DefaultFontFamilyName: case "Noto Mono": { diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 43948e9229..c457a96299 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -1005,7 +1005,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(cluster)); - Assert.Equal(currentX, distance, 5); + Assert.Equal(currentX, distance); currentX += glyphAdvance; From 4f79ac8a1c1cdecc71e89e4d94d98082ec54c8ea Mon Sep 17 00:00:00 2001 From: Benedikt Date: Tue, 2 Aug 2022 13:25:57 +0200 Subject: [PATCH 210/240] Fix precession errors --- src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs | 6 +++--- .../Media/TextFormatting/TextLayoutTests.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 6c6939d2a0..fa1ab6fd29 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -357,7 +357,7 @@ namespace Avalonia.Media.TextFormatting if (currentPosition + currentRun.TextSourceLength >= characterIndex && TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, flowDirection, out var distance, out _)) { - return currentDistance + distance; + return Math.Max(0, currentDistance + distance); } //No hit hit found so we add the full width @@ -382,7 +382,7 @@ namespace Avalonia.Media.TextFormatting distance = currentGlyphRun.Size.Width - distance; } - return currentDistance - distance; + return Math.Max(0, currentDistance - distance); } //No hit hit found so we add the full width @@ -392,7 +392,7 @@ namespace Avalonia.Media.TextFormatting } } - return currentDistance; + return Math.Max(0, currentDistance); } private static bool TryGetDistanceFromCharacterHit( diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index c457a96299..43948e9229 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -1005,7 +1005,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(cluster)); - Assert.Equal(currentX, distance); + Assert.Equal(currentX, distance, 5); currentX += glyphAdvance; From fe79cee6726a76e875a3184218106d7b3b6dc6de Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Tue, 2 Aug 2022 16:11:08 +0300 Subject: [PATCH 211/240] Fix Tests. --- .../ListBoxTests_Single.cs | 133 +++-- .../Primitives/SelectingItemsControlTests.cs | 89 +-- .../SelectingItemsControlTests_Multiple.cs | 542 ++++++++++-------- 3 files changed, 422 insertions(+), 342 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index 8f795104bf..bf516748cc 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -5,10 +5,12 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -60,104 +62,123 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - }; - - ApplyTemplate(target); - _mouse.Click(target.Presenter.Panel.Children[0]); - - Assert.Equal(0, target.SelectedIndex); + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + _mouse.Click(target.Presenter.Panel.Children[0]); + + Assert.Equal(0, target.SelectedIndex); + } } [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - }; - - ApplyTemplate(target); - target.SelectedIndex = 0; + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + target.SelectedIndex = 0; - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(0, target.SelectedIndex); + Assert.Equal(0, target.SelectedIndex); + } } [Fact] public void Clicking_Item_Should_Select_It_When_SelectionMode_Toggle() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Single | SelectionMode.Toggle, - }; - - ApplyTemplate(target); + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + SelectionMode = SelectionMode.Single | SelectionMode.Toggle, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(0, target.SelectedIndex); + Assert.Equal(0, target.SelectedIndex); + } } [Fact] public void Clicking_Selected_Item_Should_Deselect_It_When_SelectionMode_Toggle() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Toggle, - }; + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + SelectionMode = SelectionMode.Toggle, + }; - ApplyTemplate(target); - target.SelectedIndex = 0; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + target.SelectedIndex = 0; - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(-1, target.SelectedIndex); + Assert.Equal(-1, target.SelectedIndex); + } } [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It_When_SelectionMode_ToggleAlwaysSelected() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Toggle | SelectionMode.AlwaysSelected, - }; - - ApplyTemplate(target); - target.SelectedIndex = 0; + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + SelectionMode = SelectionMode.Toggle | SelectionMode.AlwaysSelected, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + target.SelectedIndex = 0; - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(0, target.SelectedIndex); + Assert.Equal(0, target.SelectedIndex); + } } [Fact] public void Clicking_Another_Item_Should_Select_It_When_SelectionMode_Toggle() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = new FuncControlTemplate(CreateListBoxTemplate), - Items = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Single | SelectionMode.Toggle, - }; - - ApplyTemplate(target); - target.SelectedIndex = 1; + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + SelectionMode = SelectionMode.Single | SelectionMode.Toggle, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + target.SelectedIndex = 1; - _mouse.Click(target.Presenter.Panel.Children[0]); + _mouse.Click(target.Presenter.Panel.Children[0]); - Assert.Equal(0, target.SelectedIndex); + Assert.Equal(0, target.SelectedIndex); + } } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 4b6b6a1182..330cbfd7b9 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -14,6 +14,7 @@ using Avalonia.Controls.Selection; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Markup.Data; using Avalonia.Platform; @@ -1115,42 +1116,48 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Setting_SelectedItem_With_Pointer_Should_Set_TabOnceActiveElement() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz " }, - }; - - Prepare(target); - _helper.Down((Interactive)target.Presenter.Panel.Children[1]); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + Prepare(target); + _helper.Down((Interactive)target.Presenter.Panel.Children[1]); - var panel = target.Presenter.Panel; + var panel = target.Presenter.Panel; - Assert.Equal( - KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel), - panel.Children[1]); + Assert.Equal( + KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel), + panel.Children[1]); + } } [Fact] public void Removing_SelectedItem_Should_Clear_TabOnceActiveElement() { - var items = new ObservableCollection(new[] { "Foo", "Bar", "Baz " }); - - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = items, - }; + var items = new ObservableCollection(new[] { "Foo", "Bar", "Baz " }); - Prepare(target); + var target = new ListBox + { + Template = Template(), + Items = items, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + Prepare(target); - _helper.Down(target.Presenter.Panel.Children[1]); + _helper.Down(target.Presenter.Panel.Children[1]); - items.RemoveAt(1); + items.RemoveAt(1); - var panel = target.Presenter.Panel; + var panel = target.Presenter.Panel; - Assert.Null(KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel)); + Assert.Null(KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel)); + } } [Fact] @@ -1230,31 +1237,37 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - }; - - Prepare(target); - _helper.Down((Interactive)target.Presenter.Panel.Children[3]); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + Prepare(target); + _helper.Down((Interactive)target.Presenter.Panel.Children[3]); - Assert.Equal(3, target.SelectedIndex); + Assert.Equal(3, target.SelectedIndex); + } } [Fact] public void Should_Apply_Selected_Pseudoclass_To_Correct_Item_When_Duplicate_Items_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - }; - - Prepare(target); - _helper.Down((Interactive)target.Presenter.Panel.Children[3]); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + Prepare(target); + _helper.Down((Interactive)target.Presenter.Panel.Children[3]); - Assert.Equal(new[] { ":pressed", ":selected" }, target.Presenter.Panel.Children[3].Classes); + Assert.Equal(new[] { ":pressed", ":selected" }, target.Presenter.Panel.Children[3].Classes); + } } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index 6b26d76371..5d2f4e2a64 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -10,8 +10,10 @@ using Avalonia.Controls.Selection; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests.Primitives @@ -701,261 +703,290 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_From_No_Selection_Selects_From_Start() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); - - var panel = target.Presenter.Panel; - - Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); - Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); + + var panel = target.Presenter.Panel; + + Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); + Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target)); + } } [Fact] public void Ctrl_Selecting_Raises_SelectionChanged_Events() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Qux" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Qux" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); - SelectionChangedEventArgs receivedArgs = null; + SelectionChangedEventArgs receivedArgs = null; - target.SelectionChanged += (_, args) => receivedArgs = args; + target.SelectionChanged += (_, args) => receivedArgs = args; - void VerifyAdded(string selection) - { - Assert.NotNull(receivedArgs); - Assert.Equal(new[] { selection }, receivedArgs.AddedItems); - Assert.Empty(receivedArgs.RemovedItems); - } + void VerifyAdded(string selection) + { + Assert.NotNull(receivedArgs); + Assert.Equal(new[] { selection }, receivedArgs.AddedItems); + Assert.Empty(receivedArgs.RemovedItems); + } - void VerifyRemoved(string selection) - { - Assert.NotNull(receivedArgs); - Assert.Equal(new[] { selection }, receivedArgs.RemovedItems); - Assert.Empty(receivedArgs.AddedItems); - } + void VerifyRemoved(string selection) + { + Assert.NotNull(receivedArgs); + Assert.Equal(new[] { selection }, receivedArgs.RemovedItems); + Assert.Empty(receivedArgs.AddedItems); + } - _helper.Click((Interactive)target.Presenter.Panel.Children[1]); + _helper.Click((Interactive)target.Presenter.Panel.Children[1]); - VerifyAdded("Bar"); + VerifyAdded("Bar"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); - VerifyAdded("Baz"); + VerifyAdded("Baz"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); - VerifyAdded("Qux"); + VerifyAdded("Qux"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); - VerifyRemoved("Bar"); + VerifyRemoved("Bar"); + } } [Fact] public void Ctrl_Selecting_SelectedItem_With_Multiple_Selection_Active_Sets_SelectedItem_To_Next_Selection() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Qux" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[1]); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); - _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); - - Assert.Equal(1, target.SelectedIndex); - Assert.Equal("Bar", target.SelectedItem); - Assert.Equal(new[] { "Bar", "Baz", "Qux" }, target.SelectedItems); - - _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); - - Assert.Equal(2, target.SelectedIndex); - Assert.Equal("Baz", target.SelectedItem); - Assert.Equal(new[] { "Baz", "Qux" }, target.SelectedItems); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Qux" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[1]); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); + + Assert.Equal(1, target.SelectedIndex); + Assert.Equal("Bar", target.SelectedItem); + Assert.Equal(new[] { "Bar", "Baz", "Qux" }, target.SelectedItems); + + _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); + + Assert.Equal(2, target.SelectedIndex); + Assert.Equal("Baz", target.SelectedItem); + Assert.Equal(new[] { "Baz", "Qux" }, target.SelectedItems); + } } [Fact] public void Ctrl_Selecting_Non_SelectedItem_With_Multiple_Selection_Active_Leaves_SelectedItem_The_Same() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[1]); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[1]); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); - Assert.Equal(1, target.SelectedIndex); - Assert.Equal("Bar", target.SelectedItem); + Assert.Equal(1, target.SelectedIndex); + Assert.Equal("Bar", target.SelectedItem); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); - Assert.Equal(1, target.SelectedIndex); - Assert.Equal("Bar", target.SelectedItem); + Assert.Equal(1, target.SelectedIndex); + Assert.Equal("Bar", target.SelectedItem); + } } [Fact] public void Should_Ctrl_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[3]); - _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); - - var panel = target.Presenter.Panel; - - Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); - Assert.Equal(new[] { 3, 4 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[3]); + _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); + + var panel = target.Presenter.Panel; + + Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); + Assert.Equal(new[] { 3, 4 }, SelectedContainers(target)); + } } [Fact] public void Should_Shift_Select_Correct_Item_When_Duplicates_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[3]); - _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); - - var panel = target.Presenter.Panel; - - Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); - Assert.Equal(new[] { 3, 4, 5 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[3]); + _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); + + var panel = target.Presenter.Panel; + + Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); + Assert.Equal(new[] { 3, 4, 5 }, SelectedContainers(target)); + } } [Fact] public void Can_Shift_Select_All_Items_When_Duplicates_Are_Present() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); - - var panel = target.Presenter.Panel; - - Assert.Equal(new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, target.SelectedItems); - Assert.Equal(new[] { 0, 1, 2, 3, 4, 5 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); + + var panel = target.Presenter.Panel; + + Assert.Equal(new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, target.SelectedItems); + Assert.Equal(new[] { 0, 1, 2, 3, 4, 5 }, SelectedContainers(target)); + } } [Fact] public void Shift_Selecting_Raises_SelectionChanged_Events() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Qux" }, - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Qux" }, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); - SelectionChangedEventArgs receivedArgs = null; + SelectionChangedEventArgs receivedArgs = null; - target.SelectionChanged += (_, args) => receivedArgs = args; + target.SelectionChanged += (_, args) => receivedArgs = args; - void VerifyAdded(params string[] selection) - { - Assert.NotNull(receivedArgs); - Assert.Equal(selection, receivedArgs.AddedItems); - Assert.Empty(receivedArgs.RemovedItems); - } + void VerifyAdded(params string[] selection) + { + Assert.NotNull(receivedArgs); + Assert.Equal(selection, receivedArgs.AddedItems); + Assert.Empty(receivedArgs.RemovedItems); + } - void VerifyRemoved(string selection) - { - Assert.NotNull(receivedArgs); - Assert.Equal(new[] { selection }, receivedArgs.RemovedItems); - Assert.Empty(receivedArgs.AddedItems); - } + void VerifyRemoved(string selection) + { + Assert.NotNull(receivedArgs); + Assert.Equal(new[] { selection }, receivedArgs.RemovedItems); + Assert.Empty(receivedArgs.AddedItems); + } - _helper.Click((Interactive)target.Presenter.Panel.Children[1]); + _helper.Click((Interactive)target.Presenter.Panel.Children[1]); - VerifyAdded("Bar"); + VerifyAdded("Bar"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Shift); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Shift); - VerifyAdded("Baz" ,"Qux"); + VerifyAdded("Baz", "Qux"); - receivedArgs = null; - _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); + receivedArgs = null; + _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); - VerifyRemoved("Qux"); + VerifyRemoved("Qux"); + } } [Fact] public void Duplicate_Items_Are_Added_To_SelectedItems_In_Order() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, - SelectionMode = SelectionMode.Multiple, - }; + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + }; - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - Assert.Equal(new[] { "Foo" }, target.SelectedItems); + Assert.Equal(new[] { "Foo" }, target.SelectedItems); - _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); - Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); + Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); - _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); - Assert.Equal(new[] { "Foo", "Bar", "Foo" }, target.SelectedItems); + Assert.Equal(new[] { "Foo", "Bar", "Foo" }, target.SelectedItems); - _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); + _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); - Assert.Equal(new[] { "Foo", "Bar", "Foo", "Bar" }, target.SelectedItems); + Assert.Equal(new[] { "Foo", "Bar", "Foo", "Bar" }, target.SelectedItems); + } } [Fact] @@ -1158,70 +1189,79 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Left_Click_On_SelectedItem_Should_Clear_Existing_Selection() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - target.SelectAll(); - - Assert.Equal(3, target.SelectedItems.Count); - - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - - Assert.Equal(1, target.SelectedItems.Count); - Assert.Equal(new[] { "Foo", }, target.SelectedItems); - Assert.Equal(new[] { 0 }, SelectedContainers(target)); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectAll(); + + Assert.Equal(3, target.SelectedItems.Count); + + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + + Assert.Equal(1, target.SelectedItems.Count); + Assert.Equal(new[] { "Foo", }, target.SelectedItems); + Assert.Equal(new[] { 0 }, SelectedContainers(target)); + } } [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - target.SelectAll(); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectAll(); - Assert.Equal(3, target.SelectedItems.Count); + Assert.Equal(3, target.SelectedItems.Count); - _helper.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); + _helper.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); - Assert.Equal(3, target.SelectedItems.Count); + Assert.Equal(3, target.SelectedItems.Count); + } } [Fact] public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Shift); - - Assert.Equal(2, target.SelectedItems.Count); - - _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right); - - Assert.Equal(1, target.SelectedItems.Count); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Shift); + + Assert.Equal(2, target.SelectedItems.Count); + + _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right); + + Assert.Equal(1, target.SelectedItems.Count); + } } [Fact] @@ -1253,41 +1293,47 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Right_Click_Should_Not_Select_Multiple() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Shift); - - Assert.Equal(1, target.SelectedItems.Count); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Shift); + + Assert.Equal(1, target.SelectedItems.Count); + } } [Fact] public void Ctrl_Right_Click_Should_Not_Select_Multiple() { - var target = new ListBox + using (UnitTestApplication.Start()) { - Template = Template(), - Items = new[] { "Foo", "Bar", "Baz" }, - ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), - SelectionMode = SelectionMode.Multiple, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - _helper.Click((Interactive)target.Presenter.Panel.Children[0]); - _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Control); - - Assert.Equal(1, target.SelectedItems.Count); + var target = new ListBox + { + Template = Template(), + Items = new[] { "Foo", "Bar", "Baz" }, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + _helper.Click((Interactive)target.Presenter.Panel.Children[0]); + _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Control); + + Assert.Equal(1, target.SelectedItems.Count); + } } [Fact] From 1731f88822a6d0a4c2e5a58b69f866212e7ff7e4 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 2 Aug 2022 20:37:55 -0400 Subject: [PATCH 212/240] Rename Default to Simple --- Avalonia.sln | 2 +- samples/BindingDemo/App.xaml | 2 +- samples/ControlCatalog/App.xaml.cs | 2 +- samples/ControlCatalog/ControlCatalog.csproj | 2 +- samples/ControlCatalog/MainView.xaml.cs | 4 +- samples/PlatformSanityChecks/App.xaml | 3 +- .../PlatformSanityChecks.csproj | 2 +- samples/Previewer/App.xaml | 5 +- samples/Previewer/Previewer.csproj | 2 +- samples/VirtualizationDemo/App.xaml | 4 +- .../VirtualizationDemo.csproj | 2 +- .../interop/Direct3DInteropSample/App.paml | 5 +- .../Direct3DInteropSample.csproj | 2 +- src/Avalonia.Controls/ProgressBar.cs | 4 - .../Avalonia.Diagnostics.csproj | 2 +- .../Diagnostics/Views/MainWindow.xaml | 2 +- .../Diagnostics/Views/MainWindow.xaml.cs | 2 +- src/Avalonia.Themes.Default/DefaultTheme.xaml | 75 ------------------- .../DefaultTheme.xaml.cs | 11 --- .../Accents/Base.xaml | 0 .../Accents/BaseDark.xaml | 0 .../Accents/BaseLight.xaml | 0 .../ApiCompatBaseline.txt | 0 .../Avalonia.Themes.Simple.csproj} | 0 .../Controls/AutoCompleteBox.xaml | 0 .../Controls/Button.xaml | 0 .../Controls/ButtonSpinner.xaml | 6 +- .../Controls/Calendar.xaml | 0 .../Controls/CalendarButton.xaml | 0 .../Controls/CalendarDatePicker.xaml | 0 .../Controls/CalendarDayButton.xaml | 0 .../Controls/CalendarItem.xaml | 0 .../Controls/CaptionButtons.xaml | 10 +-- .../Controls/Carousel.xaml | 0 .../Controls/CheckBox.xaml | 0 .../Controls/ComboBox.xaml | 0 .../Controls/ComboBoxItem.xaml | 0 .../Controls/ContentControl.xaml | 0 .../Controls/ContextMenu.xaml | 2 +- .../Controls/DataValidationErrors.xaml | 0 .../Controls/DatePicker.xaml | 22 +++--- .../Controls/DateTimePickerShared.xaml | 22 +++--- .../Controls/DropDownButton.xaml | 0 .../Controls/EmbeddableControlRoot.xaml | 0 .../Controls/Expander.xaml | 34 ++++----- .../Controls/FlyoutPresenter.xaml | 0 .../Controls/FocusAdorner.xaml | 0 .../Controls/GridSplitter.xaml | 0 .../Controls/ItemsControl.xaml | 0 .../Controls/Label.xaml | 0 .../Controls/ListBox.xaml | 0 .../Controls/ListBoxItem.xaml | 0 .../Controls/ManagedFileChooser.xaml | 0 .../Controls/Menu.xaml | 6 +- .../Controls/MenuFlyoutPresenter.xaml | 2 +- .../Controls/MenuItem.xaml | 2 +- .../Controls/NativeMenuBar.xaml | 10 +-- .../Controls/NotificationCard.xaml | 0 .../Controls/NumericUpDown.xaml | 0 .../Controls/OverlayPopupHost.xaml | 0 .../Controls/PathIcon.xaml | 0 .../Controls/PopupRoot.xaml | 0 .../Controls/ProgressBar.xaml | 0 .../Controls/RadioButton.xaml | 0 .../Controls/RepeatButton.xaml | 0 .../Controls/RichTextBlock.xaml | 0 .../Controls/ScrollBar.xaml | 0 .../Controls/ScrollViewer.xaml | 2 +- .../Controls/Separator.xaml | 0 .../Controls/SimpleControls.xaml | 74 ++++++++++++++++++ .../Controls/Slider.xaml | 0 .../Controls/SplitButton.xaml | 8 +- .../Controls/SplitView.xaml | 0 .../Controls/TabControl.xaml | 0 .../Controls/TabItem.xaml | 0 .../Controls/TabStrip.xaml | 0 .../Controls/TabStripItem.xaml | 0 .../Controls/TextBox.xaml | 14 ++-- .../Controls/TimePicker.xaml | 20 ++--- .../Controls/TitleBar.xaml | 0 .../Controls/ToggleButton.xaml | 0 .../Controls/ToggleSwitch.xaml | 0 .../Controls/ToolTip.xaml | 0 .../Controls/TransitioningContentControl.xaml | 0 .../Controls/TreeView.xaml | 0 .../Controls/TreeViewItem.xaml | 4 +- .../Controls/UserControl.xaml | 4 +- .../Controls/Window.xaml | 0 .../Controls/WindowNotificationManager.xaml | 0 .../IBitmapToImageConverter.cs | 2 +- .../InverseBooleanValueConverter.cs | 2 +- .../Properties/AssemblyInfo.cs | 3 + .../SimpleTheme.cs | 14 ++-- .../SimpleThemeMode.cs | 2 +- .../Avalonia.Designer.HostApp.csproj | 2 +- .../Utilities/UriExtensionsTests.cs | 6 +- .../Avalonia.Benchmarks.csproj | 2 +- .../Themes/ThemeBenchmark.cs | 6 +- .../Avalonia.DesignerSupport.TestApp/App.xaml | 6 +- .../Avalonia.DesignerSupport.TestApp.csproj | 2 +- .../Avalonia.Direct2D1.RenderTests.csproj | 2 +- .../Avalonia.LeakTests.csproj | 2 +- .../Avalonia.Markup.Xaml.UnitTests.csproj | 2 +- .../Xaml/BasicTests.cs | 2 +- .../Avalonia.Skia.RenderTests.csproj | 2 +- .../Avalonia.UnitTests.csproj | 2 +- tests/Avalonia.UnitTests/TestServices.cs | 19 ++--- .../Avalonia.UnitTests/UnitTestApplication.cs | 8 +- 108 files changed, 220 insertions(+), 241 deletions(-) delete mode 100644 src/Avalonia.Themes.Default/DefaultTheme.xaml delete mode 100644 src/Avalonia.Themes.Default/DefaultTheme.xaml.cs rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Accents/Base.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Accents/BaseDark.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Accents/BaseLight.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/ApiCompatBaseline.txt (100%) rename src/{Avalonia.Themes.Default/Avalonia.Themes.Default.csproj => Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj} (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/AutoCompleteBox.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Button.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ButtonSpinner.xaml (94%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Calendar.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/CalendarButton.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/CalendarDatePicker.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/CalendarDayButton.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/CalendarItem.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/CaptionButtons.xaml (93%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Carousel.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/CheckBox.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ComboBox.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ComboBoxItem.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ContentControl.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ContextMenu.xaml (95%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/DataValidationErrors.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/DatePicker.xaml (94%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/DateTimePickerShared.xaml (87%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/DropDownButton.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/EmbeddableControlRoot.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Expander.xaml (88%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/FlyoutPresenter.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/FocusAdorner.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/GridSplitter.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ItemsControl.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Label.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ListBox.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ListBoxItem.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ManagedFileChooser.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Menu.xaml (95%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/MenuFlyoutPresenter.xaml (96%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/MenuItem.xaml (98%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/NativeMenuBar.xaml (76%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/NotificationCard.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/NumericUpDown.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/OverlayPopupHost.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/PathIcon.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/PopupRoot.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ProgressBar.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/RadioButton.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/RepeatButton.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/RichTextBlock.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ScrollBar.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ScrollViewer.xaml (99%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Separator.xaml (100%) create mode 100644 src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Slider.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/SplitButton.xaml (98%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/SplitView.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TabControl.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TabItem.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TabStrip.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TabStripItem.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TextBox.xaml (96%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TimePicker.xaml (94%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TitleBar.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ToggleButton.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ToggleSwitch.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/ToolTip.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TransitioningContentControl.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TreeView.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/TreeViewItem.xaml (96%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/UserControl.xaml (90%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/Window.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/Controls/WindowNotificationManager.xaml (100%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/IBitmapToImageConverter.cs (95%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/InverseBooleanValueConverter.cs (94%) create mode 100644 src/Avalonia.Themes.Simple/Properties/AssemblyInfo.cs rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/SimpleTheme.cs (89%) rename src/{Avalonia.Themes.Default => Avalonia.Themes.Simple}/SimpleThemeMode.cs (67%) diff --git a/Avalonia.sln b/Avalonia.sln index 4999719676..35b6b2108a 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -13,7 +13,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Direct2D1", "src\W EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls", "src\Avalonia.Controls\Avalonia.Controls.csproj", "{D2221C82-4A25-4583-9B43-D791E3F6820C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Default", "src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj", "{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Simple", "src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj", "{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Diagnostics", "src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj", "{7062AE20-5DCC-4442-9645-8195BDECE63E}" EndProject diff --git a/samples/BindingDemo/App.xaml b/samples/BindingDemo/App.xaml index 3e312c8685..175e838616 100644 --- a/samples/BindingDemo/App.xaml +++ b/samples/BindingDemo/App.xaml @@ -4,6 +4,6 @@ x:Class="BindingDemo.App"> - + diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 7ebb87094a..eb8ecc3af0 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -5,7 +5,7 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml.Styling; using Avalonia.Styling; -using Avalonia.Themes.Default; +using Avalonia.Themes.Simple; using Avalonia.Themes.Fluent; using ControlCatalog.ViewModels; diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 8358fb3cd4..2654574a3e 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -29,7 +29,7 @@ - + diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 47d11738bc..d8d58edaf6 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -60,14 +60,14 @@ namespace ControlCatalog } else if (theme == CatalogTheme.DefaultLight) { - App.Default.Mode = Avalonia.Themes.Default.SimpleThemeMode.Light; + App.Default.Mode = Avalonia.Themes.Simple.SimpleThemeMode.Light; Application.Current.Styles[0] = App.DefaultLight; Application.Current.Styles[1] = App.ColorPickerDefault; Application.Current.Styles[2] = App.DataGridDefault; } else if (theme == CatalogTheme.DefaultDark) { - App.Default.Mode = Avalonia.Themes.Default.SimpleThemeMode.Dark; + App.Default.Mode = Avalonia.Themes.Simple.SimpleThemeMode.Dark; Application.Current.Styles[0] = App.DefaultDark; Application.Current.Styles[1] = App.ColorPickerDefault; Application.Current.Styles[2] = App.DataGridDefault; diff --git a/samples/PlatformSanityChecks/App.xaml b/samples/PlatformSanityChecks/App.xaml index 25bab6ae35..1b9d64fca6 100644 --- a/samples/PlatformSanityChecks/App.xaml +++ b/samples/PlatformSanityChecks/App.xaml @@ -1,6 +1,5 @@ - - + diff --git a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj index 4f7f06b529..5c743aabdb 100644 --- a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj +++ b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj @@ -7,7 +7,7 @@ - + diff --git a/samples/Previewer/App.xaml b/samples/Previewer/App.xaml index 6bae1955af..1b9d64fca6 100644 --- a/samples/Previewer/App.xaml +++ b/samples/Previewer/App.xaml @@ -1,6 +1,5 @@ - - + - \ No newline at end of file + diff --git a/samples/Previewer/Previewer.csproj b/samples/Previewer/Previewer.csproj index 98560e9ab0..2cc84168dc 100644 --- a/samples/Previewer/Previewer.csproj +++ b/samples/Previewer/Previewer.csproj @@ -10,7 +10,7 @@ - + diff --git a/samples/VirtualizationDemo/App.xaml b/samples/VirtualizationDemo/App.xaml index 3ad1dce794..0b64d0c09b 100644 --- a/samples/VirtualizationDemo/App.xaml +++ b/samples/VirtualizationDemo/App.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="VirtualizationDemo.App"> - - + + \ No newline at end of file diff --git a/samples/VirtualizationDemo/VirtualizationDemo.csproj b/samples/VirtualizationDemo/VirtualizationDemo.csproj index bd6054327f..b27cfe77e8 100644 --- a/samples/VirtualizationDemo/VirtualizationDemo.csproj +++ b/samples/VirtualizationDemo/VirtualizationDemo.csproj @@ -5,7 +5,7 @@ - + diff --git a/samples/interop/Direct3DInteropSample/App.paml b/samples/interop/Direct3DInteropSample/App.paml index d9630eef58..e6d77dfaf4 100644 --- a/samples/interop/Direct3DInteropSample/App.paml +++ b/samples/interop/Direct3DInteropSample/App.paml @@ -1,6 +1,5 @@ - - + - \ No newline at end of file + diff --git a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj index 81b94b4d09..f9ef4693d5 100644 --- a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj +++ b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 56b9caf085..91114628ee 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -119,14 +119,12 @@ namespace Avalonia.Controls nameof(Percentage), o => o.Percentage); - [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")] public static readonly DirectProperty IndeterminateStartingOffsetProperty = AvaloniaProperty.RegisterDirect( nameof(IndeterminateStartingOffset), p => p.IndeterminateStartingOffset, (p, o) => p.IndeterminateStartingOffset = o); - [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")] public static readonly DirectProperty IndeterminateEndingOffsetProperty = AvaloniaProperty.RegisterDirect( nameof(IndeterminateEndingOffset), @@ -139,14 +137,12 @@ namespace Avalonia.Controls private set { SetAndRaise(PercentageProperty, ref _percentage, value); } } - [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")] public double IndeterminateStartingOffset { get => _indeterminateStartingOffset; set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value); } - [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")] public double IndeterminateEndingOffset { get => _indeterminateEndingOffset; diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index adddf3f57b..d719135a7f 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml index 004518598c..63073952ac 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:views="clr-namespace:Avalonia.Diagnostics.Views" xmlns:diag="clr-namespace:Avalonia.Diagnostics" - xmlns:default="using:Avalonia.Themes.Default" + xmlns:default="using:Avalonia.Themes.Simple" Title="Avalonia DevTools" x:Class="Avalonia.Diagnostics.Views.MainWindow" Theme="{StaticResource {x:Type Window}}"> diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 96dc929434..c81997f2cb 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -11,7 +11,7 @@ using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Markup.Xaml; using Avalonia.Styling; -using Avalonia.Themes.Default; +using Avalonia.Themes.Simple; using Avalonia.VisualTree; namespace Avalonia.Diagnostics.Views diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml deleted file mode 100644 index 8f5bea557c..0000000000 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml.cs b/src/Avalonia.Themes.Default/DefaultTheme.xaml.cs deleted file mode 100644 index 598b418977..0000000000 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Avalonia.Styling; - -namespace Avalonia.Themes.Default -{ - /// - /// The default Avalonia theme. - /// - public class DefaultTheme : Styles - { - } -} diff --git a/src/Avalonia.Themes.Default/Accents/Base.xaml b/src/Avalonia.Themes.Simple/Accents/Base.xaml similarity index 100% rename from src/Avalonia.Themes.Default/Accents/Base.xaml rename to src/Avalonia.Themes.Simple/Accents/Base.xaml diff --git a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml b/src/Avalonia.Themes.Simple/Accents/BaseDark.xaml similarity index 100% rename from src/Avalonia.Themes.Default/Accents/BaseDark.xaml rename to src/Avalonia.Themes.Simple/Accents/BaseDark.xaml diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Simple/Accents/BaseLight.xaml similarity index 100% rename from src/Avalonia.Themes.Default/Accents/BaseLight.xaml rename to src/Avalonia.Themes.Simple/Accents/BaseLight.xaml diff --git a/src/Avalonia.Themes.Default/ApiCompatBaseline.txt b/src/Avalonia.Themes.Simple/ApiCompatBaseline.txt similarity index 100% rename from src/Avalonia.Themes.Default/ApiCompatBaseline.txt rename to src/Avalonia.Themes.Simple/ApiCompatBaseline.txt diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj similarity index 100% rename from src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj rename to src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj diff --git a/src/Avalonia.Themes.Default/Controls/AutoCompleteBox.xaml b/src/Avalonia.Themes.Simple/Controls/AutoCompleteBox.xaml similarity index 100% rename from src/Avalonia.Themes.Default/Controls/AutoCompleteBox.xaml rename to src/Avalonia.Themes.Simple/Controls/AutoCompleteBox.xaml diff --git a/src/Avalonia.Themes.Default/Controls/Button.xaml b/src/Avalonia.Themes.Simple/Controls/Button.xaml similarity index 100% rename from src/Avalonia.Themes.Default/Controls/Button.xaml rename to src/Avalonia.Themes.Simple/Controls/Button.xaml diff --git a/src/Avalonia.Themes.Default/Controls/ButtonSpinner.xaml b/src/Avalonia.Themes.Simple/Controls/ButtonSpinner.xaml similarity index 94% rename from src/Avalonia.Themes.Default/Controls/ButtonSpinner.xaml rename to src/Avalonia.Themes.Simple/Controls/ButtonSpinner.xaml index 4585fc8e56..9798e5290b 100644 --- a/src/Avalonia.Themes.Default/Controls/ButtonSpinner.xaml +++ b/src/Avalonia.Themes.Simple/Controls/ButtonSpinner.xaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:CompileBindings="True"> - @@ -38,7 +38,7 @@ IsVisible="{TemplateBinding ShowButtonSpinner}" Rows="2"> + Theme="{StaticResource SimpleButtonSpinnerRepeatButton}"> + Theme="{StaticResource SimpleButtonSpinnerRepeatButton}"> - @@ -44,7 +44,7 @@ TextElement.FontSize="10"> /// The base URL for the XAML context. - public SimpleTheme(Uri baseUri) + public SimpleTheme(Uri? baseUri = null) { - _baseUri = baseUri; + _baseUri = baseUri ?? new Uri("avares://Avalonia.Themes.Simple/"); InitStyles(_baseUri); } @@ -138,18 +138,18 @@ namespace Avalonia.Themes.Default { new StyleInclude(baseUri) { - Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml") + Source = new Uri("avares://Avalonia.Themes.Simple/Controls/SimpleControls.xaml") }, new StyleInclude(baseUri) { - Source = new Uri("avares://Avalonia.Themes.Default/Accents/Base.xaml") + Source = new Uri("avares://Avalonia.Themes.Simple/Accents/Base.xaml") } }; _simpleLight = new Styles { new StyleInclude(baseUri) { - Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseLight.xaml") + Source = new Uri("avares://Avalonia.Themes.Simple/Accents/BaseLight.xaml") } }; @@ -157,7 +157,7 @@ namespace Avalonia.Themes.Default { new StyleInclude(baseUri) { - Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseDark.xaml") + Source = new Uri("avares://Avalonia.Themes.Simple/Accents/BaseDark.xaml") } }; } diff --git a/src/Avalonia.Themes.Default/SimpleThemeMode.cs b/src/Avalonia.Themes.Simple/SimpleThemeMode.cs similarity index 67% rename from src/Avalonia.Themes.Default/SimpleThemeMode.cs rename to src/Avalonia.Themes.Simple/SimpleThemeMode.cs index be33466327..683c751f10 100644 --- a/src/Avalonia.Themes.Default/SimpleThemeMode.cs +++ b/src/Avalonia.Themes.Simple/SimpleThemeMode.cs @@ -1,4 +1,4 @@ -namespace Avalonia.Themes.Default +namespace Avalonia.Themes.Simple { public enum SimpleThemeMode { diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index 1cf68c1605..f509bb21ba 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/Avalonia.Base.UnitTests/Utilities/UriExtensionsTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/UriExtensionsTests.cs index 5c3ac6adeb..4a879c8ced 100644 --- a/tests/Avalonia.Base.UnitTests/Utilities/UriExtensionsTests.cs +++ b/tests/Avalonia.Base.UnitTests/Utilities/UriExtensionsTests.cs @@ -10,9 +10,9 @@ public class UriExtensionsTests public void Assembly_Name_From_Query_Parsed() { const string key = "assembly"; - const string value = "Avalonia.Themes.Default"; + const string value = "Avalonia.Themes.Simple"; - var uri = new Uri($"resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?{key}={value}"); + var uri = new Uri($"resm:Avalonia.Themes.Simple.Accents.BaseLight.xaml?{key}={value}"); var name = uri.GetAssemblyNameFromQuery(); Assert.Equal(value, name); @@ -21,7 +21,7 @@ public class UriExtensionsTests [Fact] public void Assembly_Name_From_Empty_Query_Not_Parsed() { - var uri = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml"); + var uri = new Uri("resm:Avalonia.Themes.Simple.Accents.BaseLight.xaml"); var name = uri.GetAssemblyNameFromQuery(); Assert.Equal(string.Empty, name); diff --git a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj index 5d17808e0c..3f4978f544 100644 --- a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj +++ b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj @@ -9,7 +9,7 @@ - + diff --git a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs index 9a5b49790d..c64931a6ef 100644 --- a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs @@ -37,8 +37,8 @@ namespace Avalonia.Benchmarks.Themes } [Benchmark] - [Arguments("avares://Avalonia.Themes.Default/Accents/BaseLight.xaml")] - [Arguments("avares://Avalonia.Themes.Default/Accents/BaseDark.xaml")] + [Arguments("avares://Avalonia.Themes.Simple/Accents/BaseLight.xaml")] + [Arguments("avares://Avalonia.Themes.Simple/Accents/BaseDark.xaml")] public bool InitDefaultTheme(string themeUri) { UnitTestApplication.Current.Styles[0] = new Styles @@ -49,7 +49,7 @@ namespace Avalonia.Benchmarks.Themes }, new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) { - Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml") + Source = new Uri("avares://Avalonia.Themes.Simple/DefaultTheme.xaml") } }; return ((IResourceHost)UnitTestApplication.Current).TryGetResource("ThemeAccentColor", out _); diff --git a/tests/Avalonia.DesignerSupport.TestApp/App.xaml b/tests/Avalonia.DesignerSupport.TestApp/App.xaml index 5a33ffff80..803cae09c8 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/App.xaml +++ b/tests/Avalonia.DesignerSupport.TestApp/App.xaml @@ -3,8 +3,8 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - - - + + + diff --git a/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj b/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj index f66b2b0457..278b0e087e 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj +++ b/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj @@ -21,7 +21,7 @@ - + diff --git a/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj b/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj index 52acc78db1..4a90da77e7 100644 --- a/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj +++ b/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj index a00b24bdd7..4572f7ae7c 100644 --- a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj +++ b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj @@ -15,7 +15,7 @@ - + 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 a0efa7bdeb..f562529cb8 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 29148e6f2e..af2435a52f 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -466,7 +466,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var xaml = @" - + "; var styles = AvaloniaRuntimeXamlLoader.Parse(xaml); diff --git a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj index d3f2b44968..5e481f21c1 100644 --- a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj +++ b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj @@ -14,7 +14,7 @@ - + diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index 52ef23c966..cb6884cad8 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index c1be745aca..6a211d238e 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -6,7 +6,7 @@ using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Styling; -using Avalonia.Themes.Default; +using Avalonia.Themes.Simple; using Avalonia.Rendering; using System.Reactive.Concurrency; using System.Collections.Generic; @@ -81,7 +81,7 @@ namespace Avalonia.UnitTests IScheduler scheduler = null, ICursorFactory standardCursorFactory = null, IStyler styler = null, - Func theme = null, + Func theme = null, IPlatformThreadingInterface threadingInterface = null, IFontManagerImpl fontManagerImpl = null, ITextShaperImpl textShaperImpl = null, @@ -122,7 +122,7 @@ namespace Avalonia.UnitTests public IScheduler Scheduler { get; } public ICursorFactory StandardCursorFactory { get; } public IStyler Styler { get; } - public Func Theme { get; } + public Func Theme { get; } public IPlatformThreadingInterface ThreadingInterface { get; } public IWindowImpl WindowImpl { get; } public IWindowingPlatform WindowingPlatform { get; } @@ -169,18 +169,9 @@ namespace Avalonia.UnitTests windowImpl: windowImpl ?? WindowImpl); } - private static Styles CreateDefaultTheme() + private static IStyle CreateDefaultTheme() { - var result = new Styles - { - new DefaultTheme(), - }; - - var baseLight = (IStyle)AvaloniaXamlLoader.Load( - new Uri("avares://Avalonia.Themes.Default/Accents/BaseLight.xaml")); - result.Add(baseLight); - - return result; + return new SimpleTheme { Mode = SimpleThemeMode.Light }; } private static IPlatformRenderInterface CreateRenderInterfaceMock() diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index 63c2832b92..260771c9ab 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -71,12 +71,16 @@ namespace Avalonia.UnitTests .Bind().ToConstant(Services.Styler) .Bind().ToConstant(Services.WindowingPlatform) .Bind().ToSingleton(); - var styles = Services.Theme?.Invoke(); + var theme = Services.Theme?.Invoke(); - if (styles != null) + if (theme is Styles styles) { Styles.AddRange(styles); } + else if (theme is not null) + { + Styles.Add(theme); + } } } } From 5ee439b5e400db88f1a620ebc60bc9222e96552e Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 2 Aug 2022 20:56:45 -0400 Subject: [PATCH 213/240] Rename remainings --- samples/ControlCatalog/App.xaml.cs | 24 +++++++++---------- samples/ControlCatalog/MainView.xaml | 4 ++-- samples/ControlCatalog/MainView.xaml.cs | 20 ++++++++-------- samples/ControlCatalog/Models/CatalogTheme.cs | 10 +++----- .../{Default => Simple}/ColorPreviewer.xaml | 0 .../{Default => Simple}/ColorSlider.xaml | 0 .../{Default => Simple}/ColorSpectrum.xaml | 0 .../Default.xaml => Simple/Simple.xaml} | 6 ++--- .../Themes/{Default.xaml => Simple.xaml} | 4 ++-- .../Diagnostics/Views/MainWindow.xaml | 5 ++-- 10 files changed, 34 insertions(+), 39 deletions(-) rename src/Avalonia.Controls.ColorPicker/Themes/{Default => Simple}/ColorPreviewer.xaml (100%) rename src/Avalonia.Controls.ColorPicker/Themes/{Default => Simple}/ColorSlider.xaml (100%) rename src/Avalonia.Controls.ColorPicker/Themes/{Default => Simple}/ColorSpectrum.xaml (100%) rename src/Avalonia.Controls.ColorPicker/Themes/{Default/Default.xaml => Simple/Simple.xaml} (94%) rename src/Avalonia.Controls.DataGrid/Themes/{Default.xaml => Simple.xaml} (99%) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index eb8ecc3af0..e936359fe4 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -23,9 +23,9 @@ namespace ControlCatalog Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml") }; - public static readonly StyleInclude ColorPickerDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) + public static readonly StyleInclude ColorPickerSimple = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) { - Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml") + Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml") }; public static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) @@ -33,16 +33,16 @@ namespace ControlCatalog Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml") }; - public static readonly StyleInclude DataGridDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) + public static readonly StyleInclude DataGridSimple = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) { - Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Default.xaml") + Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml") }; public static FluentTheme Fluent = new FluentTheme(new Uri("avares://ControlCatalog/Styles")); - public static SimpleTheme Default = new SimpleTheme(new Uri("avares://ControlCatalog/Styles")); + public static SimpleTheme Simple = new SimpleTheme(new Uri("avares://ControlCatalog/Styles")); - public static Styles DefaultLight = new Styles + public static Styles SimpleLight = new Styles { new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { @@ -56,10 +56,10 @@ namespace ControlCatalog { Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml") }, - Default + Simple }; - public static Styles DefaultDark = new Styles + public static Styles SimpleDark = new Styles { new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { @@ -73,14 +73,14 @@ namespace ControlCatalog { Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml") }, - Default + Simple }; public override void Initialize() { - Styles.Insert(0, Fluent); - Styles.Insert(1, ColorPickerFluent); - Styles.Insert(2, DataGridFluent); + Styles.Insert(0, Simple); + Styles.Insert(1, ColorPickerSimple); + Styles.Insert(2, DataGridSimple); AvaloniaXamlLoader.Load(this); } diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 7461e78c33..7f5a191519 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -187,8 +187,8 @@ FluentLight FluentDark - DefaultLight - DefaultDark + SimpleLight + SimpleDark - - - + + + diff --git a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml b/src/Avalonia.Controls.DataGrid/Themes/Simple.xaml similarity index 99% rename from src/Avalonia.Controls.DataGrid/Themes/Default.xaml rename to src/Avalonia.Controls.DataGrid/Themes/Simple.xaml index 83d9332613..6a748f399e 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Simple.xaml @@ -223,7 +223,7 @@ - @@ -270,7 +270,7 @@ BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}" Foreground="{TemplateBinding Foreground}" - Theme="{StaticResource DefaultDataGridRowGroupExpanderButtonTheme}" /> + Theme="{StaticResource SimpleDataGridRowGroupExpanderButtonTheme}" /> @@ -11,8 +10,8 @@ - - + + From fcdb22983b873969f6aaff84c62a4959356b9bb1 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 2 Aug 2022 21:03:17 -0400 Subject: [PATCH 214/240] Revert debug changes --- samples/ControlCatalog/App.xaml.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index e936359fe4..750c1082a6 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -78,9 +78,9 @@ namespace ControlCatalog public override void Initialize() { - Styles.Insert(0, Simple); - Styles.Insert(1, ColorPickerSimple); - Styles.Insert(2, DataGridSimple); + Styles.Insert(0, Fluent); + Styles.Insert(1, ColorPickerFluent); + Styles.Insert(2, DataGridFluent); AvaloniaXamlLoader.Load(this); } From a9910501ec5e67e06309847bfaf321bcf62fea93 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 2 Aug 2022 21:32:04 -0400 Subject: [PATCH 215/240] Fix tests --- samples/VirtualizationDemo/App.xaml | 16 +++++----- .../Themes/ThemeBenchmark.cs | 29 ++++++++----------- .../Avalonia.DesignerSupport.TestApp/App.xaml | 15 ++++------ 3 files changed, 25 insertions(+), 35 deletions(-) diff --git a/samples/VirtualizationDemo/App.xaml b/samples/VirtualizationDemo/App.xaml index 0b64d0c09b..eb5f0e4dca 100644 --- a/samples/VirtualizationDemo/App.xaml +++ b/samples/VirtualizationDemo/App.xaml @@ -1,9 +1,7 @@ - - - - - - \ No newline at end of file + + + + + diff --git a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs index c64931a6ef..aed8a38d08 100644 --- a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs @@ -4,6 +4,8 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml.Styling; using Avalonia.Platform; using Avalonia.Styling; +using Avalonia.Themes.Fluent; +using Avalonia.Themes.Simple; using Avalonia.UnitTests; using BenchmarkDotNet.Attributes; @@ -25,32 +27,25 @@ namespace Avalonia.Benchmarks.Themes } [Benchmark] - [Arguments("avares://Avalonia.Themes.Fluent/FluentDark.xaml")] - [Arguments("avares://Avalonia.Themes.Fluent/FluentLight.xaml")] - public bool InitFluentTheme(string themeUri) + [Arguments(FluentThemeMode.Dark)] + [Arguments(FluentThemeMode.Light)] + public bool InitFluentTheme(FluentThemeMode mode) { - UnitTestApplication.Current.Styles[0] = new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) + UnitTestApplication.Current.Styles[0] = new FluentTheme(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) { - Source = new Uri(themeUri) + Mode = mode }; return ((IResourceHost)UnitTestApplication.Current).TryGetResource("SystemAccentColor", out _); } [Benchmark] - [Arguments("avares://Avalonia.Themes.Simple/Accents/BaseLight.xaml")] - [Arguments("avares://Avalonia.Themes.Simple/Accents/BaseDark.xaml")] - public bool InitDefaultTheme(string themeUri) + [Arguments(SimpleThemeMode.Dark)] + [Arguments(SimpleThemeMode.Light)] + public bool InitDefaultTheme(SimpleThemeMode mode) { - UnitTestApplication.Current.Styles[0] = new Styles + UnitTestApplication.Current.Styles[0] = new SimpleTheme(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) { - new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) - { - Source = new Uri(themeUri) - }, - new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) - { - Source = new Uri("avares://Avalonia.Themes.Simple/DefaultTheme.xaml") - } + Mode = mode }; return ((IResourceHost)UnitTestApplication.Current).TryGetResource("ThemeAccentColor", out _); } diff --git a/tests/Avalonia.DesignerSupport.TestApp/App.xaml b/tests/Avalonia.DesignerSupport.TestApp/App.xaml index 803cae09c8..ad32431b37 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/App.xaml +++ b/tests/Avalonia.DesignerSupport.TestApp/App.xaml @@ -1,10 +1,7 @@ - - - - - - + + + + From e94eb0cbfd6bc32e588413c132fc7f54e641089c Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 2 Aug 2022 23:49:17 -0400 Subject: [PATCH 216/240] More simple theme renaming --- src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs | 4 ++-- src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml | 2 +- src/Avalonia.ReactiveUI/TransitioningContentControl.cs | 2 +- tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs | 2 +- tests/Avalonia.LeakTests/ControlTests.cs | 2 +- tests/Avalonia.UnitTests/TestServices.cs | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 39a83c00c4..4b41ecf328 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -326,8 +326,8 @@ namespace Avalonia.Build.Tasks var op = i[c].Operand as MethodReference; // TODO: Throw an error - // This usually happens when same XAML resource was added twice for some weird reason - // We currently support it for dual-named default theme resource + // This usually happens when the same XAML resource was added twice for some weird reason + // We currently support it for dual-named simple theme resources if (op != null && op.Name == TrampolineName) { diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml index 697a94421d..1e507a91fe 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml @@ -41,7 +41,7 @@ - + diff --git a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs index d26e90b2da..2bbf87590e 100644 --- a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs +++ b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs @@ -57,7 +57,7 @@ namespace Avalonia.ReactiveUI /// /// TransitioningContentControl uses the default ContentControl - /// template from Avalonia default theme. + /// template from Avalonia simple theme. /// Type IStyleable.StyleKey => typeof(ContentControl); diff --git a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs index aed8a38d08..86ba4c6005 100644 --- a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs @@ -41,7 +41,7 @@ namespace Avalonia.Benchmarks.Themes [Benchmark] [Arguments(SimpleThemeMode.Dark)] [Arguments(SimpleThemeMode.Light)] - public bool InitDefaultTheme(SimpleThemeMode mode) + public bool InitSimpleTheme(SimpleThemeMode mode) { UnitTestApplication.Current.Styles[0] = new SimpleTheme(new Uri("resm:Styles?assembly=Avalonia.Benchmarks")) { diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index a7474dc92e..2a77bd55fc 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -313,7 +313,7 @@ namespace Avalonia.LeakTests var border = textBox.GetTemplateChildren().FirstOrDefault(x => x.Name == "border"); // The TextBox should have subscriptions to its Classes collection from the - // default theme. + // simple theme. Assert.NotEqual(0, textBox.Classes.ListenerCount); // Clear the content and ensure the TextBox is removed. diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index 6a211d238e..49da2794c1 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -24,7 +24,7 @@ namespace Avalonia.UnitTests renderInterface: new MockPlatformRenderInterface(), standardCursorFactory: Mock.Of(), styler: new Styler(), - theme: () => CreateDefaultTheme(), + theme: () => CreateSimpleTheme(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true), fontManagerImpl: new MockFontManagerImpl(), textShaperImpl: new MockTextShaperImpl(), @@ -169,7 +169,7 @@ namespace Avalonia.UnitTests windowImpl: windowImpl ?? WindowImpl); } - private static IStyle CreateDefaultTheme() + private static IStyle CreateSimpleTheme() { return new SimpleTheme { Mode = SimpleThemeMode.Light }; } From 687b5f9ea0a8cdba122e7e983f379cfad713366c Mon Sep 17 00:00:00 2001 From: robloo Date: Wed, 3 Aug 2022 00:28:53 -0400 Subject: [PATCH 217/240] Fix comment for TransitioningContentControl which uses whatever default theme is loaded Co-authored-by: Max Katz --- src/Avalonia.ReactiveUI/TransitioningContentControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs index 2bbf87590e..d26e90b2da 100644 --- a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs +++ b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs @@ -57,7 +57,7 @@ namespace Avalonia.ReactiveUI /// /// TransitioningContentControl uses the default ContentControl - /// template from Avalonia simple theme. + /// template from Avalonia default theme. /// Type IStyleable.StyleKey => typeof(ContentControl); From ce966f72e3fcd5474db30f2a91ba7660c5f65896 Mon Sep 17 00:00:00 2001 From: robloo Date: Wed, 3 Aug 2022 00:29:37 -0400 Subject: [PATCH 218/240] Fix comment which is correct as "default theme" Co-authored-by: Max Katz --- tests/Avalonia.LeakTests/ControlTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 2a77bd55fc..a7474dc92e 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -313,7 +313,7 @@ namespace Avalonia.LeakTests var border = textBox.GetTemplateChildren().FirstOrDefault(x => x.Name == "border"); // The TextBox should have subscriptions to its Classes collection from the - // simple theme. + // default theme. Assert.NotEqual(0, textBox.Classes.ListenerCount); // Clear the content and ensure the TextBox is removed. From 7274083cd1343db196fb5d1d1e9a294c0b66cc5d Mon Sep 17 00:00:00 2001 From: robloo Date: Wed, 3 Aug 2022 00:30:39 -0400 Subject: [PATCH 219/240] Fix comment which is correct as "default theme" Co-authored-by: Max Katz --- src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 4b41ecf328..5915388822 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -327,7 +327,7 @@ namespace Avalonia.Build.Tasks // TODO: Throw an error // This usually happens when the same XAML resource was added twice for some weird reason - // We currently support it for dual-named simple theme resources + // We currently support it for dual-named default theme resources if (op != null && op.Name == TrampolineName) { From 705e9065a3b677359a9db1fadd7665dec96bb050 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 3 Aug 2022 02:28:05 -0400 Subject: [PATCH 220/240] Add more tests for lazy resources --- .../Xaml/ResourceDictionaryTests.cs | 84 +++++++++++++++++-- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 939c5319e4..74b6a1e15d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -74,13 +74,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var xaml = @" - Red + "; var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); Assert.True(resources.ContainsDeferredKey("Red")); } } + + [Fact] + public void Item_Added_To_ResourceDictionary_Is_UnDeferred_On_Read() + { + using (StyledWindow()) + { + var xaml = @" + + +"; + var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.True(resources.ContainsDeferredKey("Red")); + + Assert.IsType(resources["Red"]); + + Assert.False(resources.ContainsDeferredKey("Red")); + } + } [Fact] public void Item_Is_Added_To_Window_Resources_As_Deferred() @@ -91,7 +111,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml - Red + "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); @@ -113,7 +133,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml - Red + @@ -135,7 +155,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml "; var style = (Style)AvaloniaRuntimeXamlLoader.Load(xaml); @@ -154,7 +174,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml - Red + "; var style = (Styles)AvaloniaRuntimeXamlLoader.Load(xaml); @@ -164,6 +184,60 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } } + [Fact] + public void Item_Can_Be_StaticReferenced_As_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var windowResources = (ResourceDictionary)window.Resources; + var buttonResources = (ResourceDictionary)((Button)window.Content!).Resources; + + Assert.True(windowResources.ContainsDeferredKey("Red")); + Assert.True(buttonResources.ContainsDeferredKey("Red2")); + } + } + + [Fact] + public void Item_StaticReferenced_Is_UnDeferred_On_Read() + { + using (StyledWindow()) + { + var xaml = @" + + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var windowResources = (ResourceDictionary)window.Resources; + var buttonResources = (ResourceDictionary)((Button)window.Content!).Resources; + + Assert.IsType(buttonResources["Red2"]); + + Assert.False(windowResources.ContainsDeferredKey("Red")); + Assert.False(buttonResources.ContainsDeferredKey("Red2")); + } + } + private IDisposable StyledWindow(params (string, string)[] assets) { var services = TestServices.StyledWindow.With( From 3fd8763e807f195cee6c4313cf6e0ecf44e74aa5 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 3 Aug 2022 13:22:00 +0300 Subject: [PATCH 221/240] Use correct ToggleModifier in TreeView multiselection on MacOS. --- src/Avalonia.Controls/TreeView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index b2a188a2ea..7359f3cade 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -529,7 +529,7 @@ namespace Avalonia.Controls e.Source, true, e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), - e.KeyModifiers.HasAllFlags(KeyModifiers.Control), + e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService().CommandModifiers), point.Properties.IsRightButtonPressed); } } From 3717aec4f5f4e9e5c88d5b03b22d067fdd67a568 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 3 Aug 2022 14:32:38 +0300 Subject: [PATCH 222/240] Use GetFeature + API lease approach for accessing SKCanvas --- samples/RenderDemo/Pages/CustomSkiaPage.cs | 6 +- src/Avalonia.Base/Media/DrawingGroup.cs | 2 + .../Platform/IDrawingContextImpl.cs | 14 +++ .../Drawing/CompositionDrawingContext.cs | 2 + .../Composition/Server/DrawingContextProxy.cs | 3 + .../SceneGraph/DeferredDrawingContextImpl.cs | 2 + .../HeadlessPlatformRenderInterface.cs | 5 + src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 97 +++++++++++++++++-- .../Helpers/DrawingContextHelper.cs | 12 ++- .../Avalonia.Skia/ISkiaDrawingContextImpl.cs | 15 --- .../ISkiaSharpApiLeaseFeature.cs | 20 ++++ .../Media/DrawingContextImpl.cs | 1 + .../NullDrawingContextImpl.cs | 5 +- 13 files changed, 152 insertions(+), 32 deletions(-) delete mode 100644 src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs create mode 100644 src/Skia/Avalonia.Skia/ISkiaSharpApiLeaseFeature.cs diff --git a/samples/RenderDemo/Pages/CustomSkiaPage.cs b/samples/RenderDemo/Pages/CustomSkiaPage.cs index 9c524a7932..bf27747154 100644 --- a/samples/RenderDemo/Pages/CustomSkiaPage.cs +++ b/samples/RenderDemo/Pages/CustomSkiaPage.cs @@ -40,14 +40,16 @@ namespace RenderDemo.Pages static Stopwatch St = Stopwatch.StartNew(); public void Render(IDrawingContextImpl context) { - var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas; - if (canvas == null) + var leaseFeature = context.GetFeature(); + if (leaseFeature == null) using (var c = new DrawingContext(context, false)) { c.DrawText(_noSkia, new Point()); } else { + using var lease = leaseFeature.Lease(); + var canvas = lease.SkCanvas; canvas.Save(); // create the first shader var colors = new SKColor[] { diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs index 603bb1c1c1..e71f568207 100644 --- a/src/Avalonia.Base/Media/DrawingGroup.cs +++ b/src/Avalonia.Base/Media/DrawingGroup.cs @@ -228,6 +228,8 @@ namespace Avalonia.Media throw new NotImplementedException(); } + public object? GetFeature(Type t) => null; + public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) { throw new NotImplementedException(); diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs index d84a509234..6aa5eeea3d 100644 --- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs @@ -172,6 +172,20 @@ namespace Avalonia.Platform /// /// Custom draw operation void Custom(ICustomDrawOperation custom); + + /// + /// Attempts to get an optional feature from the drawing context implementation + /// + object? GetFeature(Type t); + } + + public static class DrawingContextImplExtensions + { + /// + /// Attempts to get an optional feature from the drawing context implementation + /// + public static T? GetFeature(this IDrawingContextImpl context) where T : class => + (T?)context.GetFeature(typeof(T)); } public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs index 01216d19ed..30b57883fc 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs @@ -156,6 +156,8 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW ++_drawOperationIndex; } + public object? GetFeature(Type t) => null; + /// public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index 7eb35a68ed..03859d241f 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -1,3 +1,4 @@ +using System; using System.Numerics; using Avalonia.Media; using Avalonia.Media.Imaging; @@ -155,6 +156,8 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont _impl.Custom(custom); } + public object? GetFeature(Type t) => _impl.GetFeature(t); + public class VisualBrushRenderer : IVisualBrushRenderer { public CompositionDrawList? VisualBrushDrawList { get; set; } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 07082e4ac3..d6766fa9b8 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -203,6 +203,8 @@ namespace Avalonia.Rendering.SceneGraph ++_drawOperationindex; } + public object? GetFeature(Type t) => null; + /// public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 5576368240..cb23c6c336 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -416,6 +416,11 @@ namespace Avalonia.Headless } + public object GetFeature(Type t) + { + return null; + } + public void DrawLine(IPen pen, Point p1, Point p2) { } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 8293769138..99ab60d1ac 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -10,6 +10,7 @@ using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.Utilities; using Avalonia.Utilities; using Avalonia.Media.Imaging; +using JetBrains.Annotations; using SkiaSharp; namespace Avalonia.Skia @@ -17,7 +18,7 @@ namespace Avalonia.Skia /// /// Skia based drawing context. /// - internal class DrawingContextImpl : IDrawingContextImpl, ISkiaDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport + internal class DrawingContextImpl : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport { private IDisposable[] _disposables; private readonly Vector _dpi; @@ -38,7 +39,8 @@ namespace Avalonia.Skia private readonly SKPaint _fillPaint = new SKPaint(); private readonly SKPaint _boxShadowPaint = new SKPaint(); private static SKShader s_acrylicNoiseShader; - private readonly ISkiaGpuRenderSession _session; + private readonly ISkiaGpuRenderSession _session; + private bool _leased = false; /// /// Context create info. @@ -83,6 +85,47 @@ namespace Avalonia.Skia public ISkiaGpuRenderSession CurrentSession; } + class SkiaLeaseFeature : ISkiaSharpApiLeaseFeature + { + private readonly DrawingContextImpl _context; + + public SkiaLeaseFeature(DrawingContextImpl context) + { + _context = context; + } + + public ISkiaSharpApiLease Lease() + { + _context.CheckLease(); + return new ApiLease(_context); + } + + class ApiLease : ISkiaSharpApiLease + { + private DrawingContextImpl _context; + private readonly SKMatrix _revertTransform; + + public ApiLease(DrawingContextImpl context) + { + _revertTransform = context.Canvas.TotalMatrix; + _context = context; + _context._leased = true; + } + + public SKCanvas SkCanvas => _context.Canvas; + public GRContext GrContext => _context.GrContext; + public SKSurface SkSurface => _context.Surface; + public double CurrentOpacity => _context._currentOpacity; + + public void Dispose() + { + _context.Canvas.SetMatrix(_revertTransform); + _context._leased = false; + _context = null; + } + } + } + /// /// Create new drawing context. /// @@ -123,20 +166,23 @@ namespace Avalonia.Skia public SKCanvas Canvas { get; } public SKSurface Surface { get; } - SKCanvas ISkiaDrawingContextImpl.SkCanvas => Canvas; - SKSurface ISkiaDrawingContextImpl.SkSurface => Surface; - GRContext ISkiaDrawingContextImpl.GrContext => _grContext; - double ISkiaDrawingContextImpl.CurrentOpacity => _currentOpacity; - + private void CheckLease() + { + if (_leased) + throw new InvalidOperationException("The underlying graphics API is currently leased"); + } + /// public void Clear(Color color) { + CheckLease(); Canvas.Clear(color.ToSKColor()); } /// public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { + CheckLease(); var drawableImage = (IDrawableBitmapImpl)source.Item; var s = sourceRect.ToSKRect(); var d = destRect.ToSKRect(); @@ -157,6 +203,7 @@ namespace Avalonia.Skia /// public void DrawBitmap(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { + CheckLease(); PushOpacityMask(opacityMask, opacityMaskRect); DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default); PopOpacityMask(); @@ -165,6 +212,7 @@ namespace Avalonia.Skia /// public void DrawLine(IPen pen, Point p1, Point p2) { + CheckLease(); using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y)))) { if (paint.Paint is object) @@ -177,6 +225,7 @@ namespace Avalonia.Skia /// public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) { + CheckLease(); var impl = (GeometryImpl) geometry; var size = geometry.Bounds.Size; @@ -260,6 +309,7 @@ namespace Avalonia.Skia { if (rect.Rect.Height <= 0 || rect.Rect.Width <= 0) return; + CheckLease(); var rc = rect.Rect.ToSKRect(); var isRounded = rect.IsRounded; @@ -296,6 +346,7 @@ namespace Avalonia.Skia { if (rect.Rect.Height <= 0 || rect.Rect.Width <= 0) return; + CheckLease(); // Arbitrary chosen values // On OSX Skia breaks OpenGL context when asked to draw, e. g. (0, 0, 623, 6666600) rect if (rect.Rect.Height > 8192 || rect.Rect.Width > 8192) @@ -421,7 +472,8 @@ namespace Avalonia.Skia { if (rect.Height <= 0 || rect.Width <= 0) return; - + CheckLease(); + var rc = rect.ToSKRect(); if (brush != null) @@ -447,6 +499,7 @@ namespace Avalonia.Skia /// public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { + CheckLease(); using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Size)) { var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl; @@ -459,18 +512,21 @@ namespace Avalonia.Skia /// public IDrawingContextLayerImpl CreateLayer(Size size) { + CheckLease(); return CreateRenderTarget(size, true); } /// public void PushClip(Rect clip) { + CheckLease(); Canvas.Save(); Canvas.ClipRect(clip.ToSKRect()); } public void PushClip(RoundedRect clip) { + CheckLease(); Canvas.Save(); Canvas.ClipRoundRect(clip.ToSKRoundRect(), antialias:true); } @@ -478,12 +534,14 @@ namespace Avalonia.Skia /// public void PopClip() { + CheckLease(); Canvas.Restore(); } /// public void PushOpacity(double opacity) { + CheckLease(); _opacityStack.Push(_currentOpacity); _currentOpacity *= opacity; } @@ -491,6 +549,7 @@ namespace Avalonia.Skia /// public void PopOpacity() { + CheckLease(); _currentOpacity = _opacityStack.Pop(); } @@ -499,6 +558,7 @@ namespace Avalonia.Skia { if(_disposed) return; + CheckLease(); try { if (_grContext != null) @@ -523,6 +583,7 @@ namespace Avalonia.Skia /// public void PushGeometryClip(IGeometryImpl clip) { + CheckLease(); Canvas.Save(); Canvas.ClipPath(((GeometryImpl)clip).EffectivePath, SKClipOperation.Intersect, true); } @@ -530,12 +591,14 @@ namespace Avalonia.Skia /// public void PopGeometryClip() { + CheckLease(); Canvas.Restore(); } /// public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) { + CheckLease(); _blendingModeStack.Push(_currentBlendingMode); _currentBlendingMode = blendingMode; } @@ -543,14 +606,20 @@ namespace Avalonia.Skia /// public void PopBitmapBlendMode() { + CheckLease(); _currentBlendingMode = _blendingModeStack.Pop(); } - public void Custom(ICustomDrawOperation custom) => custom.Render(this); + public void Custom(ICustomDrawOperation custom) + { + CheckLease(); + custom.Render(this); + } /// public void PushOpacityMask(IBrush mask, Rect bounds) { + CheckLease(); // TODO: This should be disposed var paint = new SKPaint(); @@ -561,6 +630,7 @@ namespace Avalonia.Skia /// public void PopOpacityMask() { + CheckLease(); using (var paint = new SKPaint { BlendMode = SKBlendMode.DstIn }) { Canvas.SaveLayer(paint); @@ -580,6 +650,7 @@ namespace Avalonia.Skia get { return _currentTransform; } set { + CheckLease(); if (_currentTransform == value) return; @@ -596,6 +667,14 @@ namespace Avalonia.Skia } } + [CanBeNull] + public object GetFeature(Type t) + { + if (t == typeof(ISkiaSharpApiLeaseFeature)) + return new SkiaLeaseFeature(this); + return null; + } + /// /// Configure paint wrapper for using gradient brush. /// diff --git a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs index d0b45b7c5d..a078c364a2 100644 --- a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs +++ b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs @@ -33,7 +33,7 @@ namespace Avalonia.Skia.Helpers /// Unsupported - Wraps a GPU Backed SkiaSurface in an Avalonia DrawingContext. /// [Obsolete] - public static ISkiaDrawingContextImpl WrapSkiaSurface(this SKSurface surface, GRContext grContext, Vector dpi, params IDisposable[] disposables) + public static IDrawingContextImpl WrapSkiaSurface(this SKSurface surface, GRContext grContext, Vector dpi, params IDisposable[] disposables) { var createInfo = new DrawingContextImpl.CreateInfo { @@ -50,7 +50,7 @@ namespace Avalonia.Skia.Helpers /// Unsupported - Wraps a non-GPU Backed SkiaSurface in an Avalonia DrawingContext. /// [Obsolete] - public static ISkiaDrawingContextImpl WrapSkiaSurface(this SKSurface surface, Vector dpi, params IDisposable[] disposables) + public static IDrawingContextImpl WrapSkiaSurface(this SKSurface surface, Vector dpi, params IDisposable[] disposables) { var createInfo = new DrawingContextImpl.CreateInfo { @@ -63,7 +63,7 @@ namespace Avalonia.Skia.Helpers } [Obsolete] - public static ISkiaDrawingContextImpl CreateDrawingContext(Size size, Vector dpi, GRContext grContext = null) + public static IDrawingContextImpl CreateDrawingContext(Size size, Vector dpi, GRContext grContext = null) { if (grContext is null) { @@ -90,9 +90,11 @@ namespace Avalonia.Skia.Helpers } [Obsolete] - public static void DrawTo(this ISkiaDrawingContextImpl source, ISkiaDrawingContextImpl destination, SKPaint paint = null) + public static void DrawTo(this IDrawingContextImpl source, IDrawingContextImpl destination, SKPaint paint = null) { - destination.SkCanvas.DrawSurface(source.SkSurface, new SKPoint(0, 0), paint); + var src = (DrawingContextImpl)source; + var dst = (DrawingContextImpl)destination; + dst.Canvas.DrawSurface(src.Surface, new SKPoint(0, 0), paint); } } } diff --git a/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs b/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs deleted file mode 100644 index 1b60154d46..0000000000 --- a/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Avalonia.Metadata; -using Avalonia.Platform; -using SkiaSharp; - -namespace Avalonia.Skia -{ - [Unstable] - public interface ISkiaDrawingContextImpl : IDrawingContextImpl - { - SKCanvas SkCanvas { get; } - GRContext GrContext { get; } - SKSurface SkSurface { get; } - double CurrentOpacity { get; } - } -} diff --git a/src/Skia/Avalonia.Skia/ISkiaSharpApiLeaseFeature.cs b/src/Skia/Avalonia.Skia/ISkiaSharpApiLeaseFeature.cs new file mode 100644 index 0000000000..b3966c0324 --- /dev/null +++ b/src/Skia/Avalonia.Skia/ISkiaSharpApiLeaseFeature.cs @@ -0,0 +1,20 @@ +using System; +using Avalonia.Metadata; +using SkiaSharp; + +namespace Avalonia.Skia; + +[Unstable] +public interface ISkiaSharpApiLeaseFeature +{ + public ISkiaSharpApiLease Lease(); +} + +[Unstable] +public interface ISkiaSharpApiLease : IDisposable +{ + SKCanvas SkCanvas { get; } + GRContext GrContext { get; } + SKSurface SkSurface { get; } + double CurrentOpacity { get; } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index a7f1d9c3e5..180ae491b3 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -614,5 +614,6 @@ namespace Avalonia.Direct2D1.Media } public void Custom(ICustomDrawOperation custom) => custom.Render(this); + public object GetFeature(Type t) => null; } } diff --git a/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs b/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs index 6fb2a5ac24..8436881122 100644 --- a/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs +++ b/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs @@ -1,4 +1,5 @@ -using Avalonia.Media; +using System; +using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; @@ -99,5 +100,7 @@ namespace Avalonia.Benchmarks public void Custom(ICustomDrawOperation custom) { } + + public object GetFeature(Type t) => null; } } From a30d73eb5ed9433cff2d212b21fb9eae93e7b54b Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 3 Aug 2022 14:38:45 +0300 Subject: [PATCH 223/240] Fix tests. --- .../TreeViewTests.cs | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index d784caf2db..9cf21423a3 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -18,6 +18,7 @@ using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; using JetBrains.Annotations; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -885,28 +886,31 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - var tree = CreateTestTreeData(); - var target = new TreeView + using (UnitTestApplication.Start()) { - Template = CreateTreeViewTemplate(), - Items = tree, - SelectionMode = SelectionMode.Multiple, - }; - - var visualRoot = new TestRoot(); - visualRoot.Child = target; + var tree = CreateTestTreeData(); + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = tree, + SelectionMode = SelectionMode.Multiple, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + var visualRoot = new TestRoot(); + visualRoot.Child = target; - CreateNodeDataTemplate(target); - ApplyTemplates(target); - target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); - target.SelectAll(); + CreateNodeDataTemplate(target); + ApplyTemplates(target); + target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); + target.SelectAll(); - AssertChildrenSelected(target, tree[0]); - Assert.Equal(5, target.SelectedItems.Count); + AssertChildrenSelected(target, tree[0]); + Assert.Equal(5, target.SelectedItems.Count); - _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); + _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); - Assert.Equal(5, target.SelectedItems.Count); + Assert.Equal(5, target.SelectedItems.Count); + } } [Fact] From fffdcfcc2a8c49f16d9f1c363c774e56e20d685b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 3 Aug 2022 14:05:55 +0200 Subject: [PATCH 224/240] Fix fluent ButtonSpinner theme. The button was greying out its borders on pointer-over. --- .../Controls/ButtonSpinner.xaml | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml index 855dc5363e..aa55065f6d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml @@ -41,13 +41,31 @@ M0,9 L10,0 20,9 19,10 10,2 1,10 z M0,1 L10,10 20,1 19,0 10,8 1,0 z - - + - + + + + + + + + + @@ -83,7 +101,6 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness, Converter={StaticResource ButtonSpinnerLeftThickness}}" - CornerRadius="0" VerticalAlignment="Stretch" VerticalContentAlignment="Center" Foreground="{TemplateBinding Foreground}" @@ -99,7 +116,6 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness, Converter={StaticResource ButtonSpinnerLeftThickness}}" - CornerRadius="0" VerticalAlignment="Stretch" VerticalContentAlignment="Center" Foreground="{TemplateBinding Foreground}" From ad518ac6787ab8265adcd1a4eddc70c390590ca6 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 3 Aug 2022 15:00:42 +0300 Subject: [PATCH 225/240] Add backpressure for sending batches to the render thread --- .../Rendering/Composition/CompositingRenderer.cs | 2 +- src/Avalonia.Base/Rendering/Composition/Compositor.cs | 11 ++++++++++- .../Rendering/Composition/Server/ServerCompositor.cs | 3 +-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index db773bc43c..9aa3c25425 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -71,7 +71,7 @@ public class CompositingRenderer : IRendererWithCompositor if(_queuedUpdate) return; _queuedUpdate = true; - Dispatcher.UIThread.Post(_update, DispatcherPriority.Composition); + _compositor.InvokeWhenReadyForNextCommit(_update); } /// diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index 45212d0f36..f812acc016 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -33,6 +33,7 @@ namespace Avalonia.Rendering.Composition internal IEasing DefaultEasing { get; } private List? _invokeOnNextCommit; private readonly Stack> _invokeListPool = new(); + private Task? _lastBatchCompleted; /// /// Creates a new compositor on a specified render loop that would use a particular GPU @@ -86,7 +87,7 @@ namespace Avalonia.Rendering.Composition if (_invokeOnNextCommit != null) ScheduleCommitCallbacks(batch.Completed); - return batch.Completed; + return _lastBatchCompleted = batch.Completed; } async void ScheduleCommitCallbacks(Task task) @@ -139,5 +140,13 @@ namespace Avalonia.Rendering.Composition _invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new(); _invokeOnNextCommit.Add(action); } + + public void InvokeWhenReadyForNextCommit(Action action) + { + if (_lastBatchCompleted == null || _lastBatchCompleted.IsCompleted) + Dispatcher.UIThread.Post(action, DispatcherPriority.Composition); + else + _lastBatchCompleted.ContinueWith(_ => Dispatcher.UIThread.Post(action, DispatcherPriority.Composition)); + } } } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs index 621bc84f4a..bfc2b2d626 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs @@ -108,6 +108,7 @@ namespace Avalonia.Rendering.Composition.Server private void RenderCore() { ApplyPendingBatches(); + CompletePendingBatches(); foreach(var animation in _activeAnimations) _animationsToUpdate.Add(animation); @@ -119,8 +120,6 @@ namespace Avalonia.Rendering.Composition.Server foreach (var t in _activeTargets) t.Render(); - - CompletePendingBatches(); } public void AddCompositionTarget(ServerCompositionTarget target) From 0d472a100b6fc191d69675512028c4e770045081 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 3 Aug 2022 15:57:13 +0300 Subject: [PATCH 226/240] Use static callback for ContinueWith --- src/Avalonia.Base/Rendering/Composition/Compositor.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index f812acc016..10360f7874 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -146,7 +146,9 @@ namespace Avalonia.Rendering.Composition if (_lastBatchCompleted == null || _lastBatchCompleted.IsCompleted) Dispatcher.UIThread.Post(action, DispatcherPriority.Composition); else - _lastBatchCompleted.ContinueWith(_ => Dispatcher.UIThread.Post(action, DispatcherPriority.Composition)); + _lastBatchCompleted.ContinueWith( + static (_, state) => Dispatcher.UIThread.Post((Action)state!, DispatcherPriority.Composition), + action); } } } From 3d327bc046ab6295fac0634b9da06fec92a8d493 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 3 Aug 2022 18:50:23 +0300 Subject: [PATCH 227/240] Fix and optimize StringFormatConverter. StringFormatConverter should validate values and return AvaloniaProperty.UnsetValue in case of incorrect value or exception. --- .../Converters/StringFormatConverter.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Converters/StringFormatConverter.cs b/src/Avalonia.Controls/Converters/StringFormatConverter.cs index ae920dac7e..7075d3de99 100644 --- a/src/Avalonia.Controls/Converters/StringFormatConverter.cs +++ b/src/Avalonia.Controls/Converters/StringFormatConverter.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; -using Avalonia.Data; using Avalonia.Data.Converters; namespace Avalonia.Controls.Converters; @@ -15,13 +13,25 @@ public class StringFormatConverter : IMultiValueConverter { public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) { - try - { - return string.Format((string)values[0]!, values.Skip(1).ToArray()); - } - catch (Exception e) + if (values != null && + values.Count == 5 && + values[0] is string format && + values[1] is double && + values[2] is double && + values[3] is double && + values[4] is double) + { - return new BindingNotification(e, BindingErrorType.Error); + + try + { + return string.Format(format, values[1], values[2], values[3], values[4]); + } + catch + { + return AvaloniaProperty.UnsetValue; + } } + return AvaloniaProperty.UnsetValue; } } From d4840a55bc1037970cc8cc2e2125fb0446741ebb Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 2 Aug 2022 18:31:57 +0300 Subject: [PATCH 228/240] add failing test for #8669 --- .../ListBoxTests.cs | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index afa153a593..f6d96edb99 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -9,7 +9,6 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Styling; -using Avalonia.Threading; using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; @@ -19,7 +18,7 @@ namespace Avalonia.Controls.UnitTests public class ListBoxTests { private MouseTestHelper _mouse = new MouseTestHelper(); - + [Fact] public void Should_Use_ItemTemplate_To_Create_Item_Content() { @@ -433,6 +432,47 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void ListBox_Should_Be_Valid_After_Remove_Of_Item_In_NonVisibleArea() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = new AvaloniaList(Enumerable.Range(1, 30).Select(v => v.ToString())); + + var wnd = new Window() { Width = 100, Height = 100, IsVisible = true }; + + var target = new ListBox() + { + AutoScrollToSelectedItem = true, + Height = 100, + Width = 50, + VirtualizationMode = ItemVirtualizationMode.Simple, + ItemTemplate = new FuncDataTemplate((c, _) => new Border() { Height = 10 }), + Items = items, + }; + wnd.Content = target; + + var lm = wnd.LayoutManager; + + lm.ExecuteInitialLayoutPass(); + + //select last / scroll to last item + target.SelectedItem = items.Last(); + + lm.ExecuteLayoutPass(); + + //remove the first item (in non realized area of the listbox) + items.Remove("1"); + lm.ExecuteLayoutPass(); + + Assert.Equal("30", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 1).DataContext); + Assert.Equal("29", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 2).DataContext); + Assert.Equal("28", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 3).DataContext); + Assert.Equal("27", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 4).DataContext); + Assert.Equal("26", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 5).DataContext); + } + } + [Fact] public void Clicking_Item_Should_Raise_BringIntoView_For_Correct_Control() { @@ -656,7 +696,6 @@ namespace Avalonia.Controls.UnitTests public string Value { get; } } - [Fact] public void SelectedItem_Validation() { @@ -670,11 +709,11 @@ namespace Avalonia.Controls.UnitTests }; Prepare(target); - + var exception = new System.InvalidCastException("failed validation"); var textObservable = new BehaviorSubject(new BindingNotification(exception, BindingErrorType.DataValidationError)); target.Bind(ComboBox.SelectedItemProperty, textObservable); - + Assert.True(DataValidationErrors.GetHasErrors(target)); Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception })); } From d68efa1cd4f89142eb7cf159425ea6939d978e02 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Thu, 4 Aug 2022 15:07:23 +0300 Subject: [PATCH 229/240] fix for #8669 --- src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index 39a512a773..4db2ec6d50 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -187,7 +187,7 @@ namespace Avalonia.Controls.Presenters break; case NotifyCollectionChangedAction.Remove: - if ((e.OldStartingIndex >= FirstIndex && e.OldStartingIndex < NextIndex) || + if (e.OldStartingIndex < NextIndex || panel.Children.Count > ItemCount) { RecycleContainersOnRemove(); From 6520722e5488350433e525232877e139ad57cf53 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 3 Aug 2022 03:20:37 -0400 Subject: [PATCH 230/240] Attempt to not deferr value types --- ...valoniaXamlIlDeferredResourceTransformer.cs | 10 ++++++++++ .../Xaml/ResourceDictionaryTests.cs | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs index 099878df08..662263e513 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs @@ -15,6 +15,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2) return node; + if (!ShouldBeDeferred(pa.Values[1])) + return node; + var types = context.GetAvaloniaTypes(); if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content") @@ -37,6 +40,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers return node; } + private static bool ShouldBeDeferred(IXamlAstValueNode node) + { + // XAML compiler is currently strict about value types, allowing them to be created only through converters. + // At the moment it should be safe to not defer structs. + return !node.Type.GetClrType().IsValueType; + } + class AdderSetter : IXamlPropertySetter, IXamlEmitablePropertySetter { private readonly IXamlMethod _getter; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 74b6a1e15d..19fb88b391 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -3,6 +3,7 @@ using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Styling; using Avalonia.UnitTests; using Xunit; @@ -237,6 +238,23 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.False(buttonResources.ContainsDeferredKey("Red2")); } } + + [Fact] + public void Value_Type_With_Parse_Should_Not_Be_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + Red +"; + var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.False(resources.ContainsDeferredKey("Red")); + Assert.IsType(resources["Red"]); + } + } private IDisposable StyledWindow(params (string, string)[] assets) { From 41d31d8857dec976aa293d8070ea69296f84c2d1 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 3 Aug 2022 03:53:01 -0400 Subject: [PATCH 231/240] Add thickness test as well --- .../Xaml/ResourceDictionaryTests.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 19fb88b391..5066341bbb 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -240,7 +240,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } [Fact] - public void Value_Type_With_Parse_Should_Not_Be_Deferred() + public void Value_Type_With_Parse_Converter_Should_Not_Be_Deferred() { using (StyledWindow()) { @@ -255,6 +255,23 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.IsType(resources["Red"]); } } + + [Fact] + public void Value_Type_With_Ctor_Converter_Should_Not_Be_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + 1 1 1 1 +"; + var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.False(resources.ContainsDeferredKey("Margin")); + Assert.IsType(resources["Margin"]); + } + } private IDisposable StyledWindow(params (string, string)[] assets) { From 0360af6780f476f195142fa2772fc9f008e9da47 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 4 Aug 2022 18:11:07 +0200 Subject: [PATCH 232/240] Added failing test for #8672. --- .../Data/TemplateBindingTests.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs index d9ea3e374c..979dbec674 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using Avalonia.Controls; @@ -217,6 +218,37 @@ namespace Avalonia.Markup.UnitTests.Data } } + [Fact] + public void Should_Not_Pass_UnsetValue_To_MultiBinding_During_ApplyTemplate() + { + var converter = new MultiConverter(); + var source = new Button + { + Content = "foo", + Template = new FuncControlTemplate