From 493128651fdefdd8b26b589c355f60ea7544f352 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 22 Jul 2022 13:24:48 +0200 Subject: [PATCH 01/61] Add x:SetterTargetType --- .../AvaloniaXamlIlSetterTransformer.cs | 55 +++++++++++++++++-- 1 file changed, 50 insertions(+), 5 deletions(-) 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 6da95be1c1..d6251badd5 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using XamlX; using XamlX.Ast; using XamlX.Emit; using XamlX.IL; @@ -17,10 +18,55 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers && on.Type.GetClrType().FullName == "Avalonia.Styling.Setter")) return node; - var targetTypeNode = context.ParentNodes() + IXamlType targetType = null; + IXamlLineInfo lineInfo = null; + + var styleParent = context.ParentNodes() .OfType() - .FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style) ?? - throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType", node); + .FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style); + + if (styleParent != null) + { + var selectorProperty = styleParent.Children.OfType() + .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Selector"); + + if (selectorProperty == null) + throw new XamlParseException( + "Can not find parent Style Selector", node); + + var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode; + + targetType = selector?.TargetType + ?? throw new XamlParseException("Can not resolve parent Style Selector type", node); + lineInfo = selector; + } + else + { + foreach (var p in context.ParentNodes().OfType()) + { + for (var index = 0; index < p.Children.Count; index++) + { + if (p.Children[index] is XamlAstXmlDirective d && + d.Namespace == XamlNamespaces.Xaml2006 && + d.Name == "SetterTargetType") + { + //p.Children.RemoveAt(index); + + targetType = context.Configuration.TypeSystem.GetType(((XamlAstTextNode)d.Values[0]).Text); + lineInfo = d; + + break; + } + } + + if (targetType != null) break; + } + } + + if (targetType == null) + { + throw new XamlParseException("Could not determine target type of Setter", node); + } IXamlType propType = null; var property = @on.Children.OfType() @@ -31,9 +77,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (propertyName == null) throw new XamlParseException("Setter.Property must be a string", node); - var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName, - new XamlAstClrTypeReference(targetTypeNode, targetTypeNode.TargetType.GetClrType(), false), property.Values[0]); + new XamlAstClrTypeReference(lineInfo, targetType, false), property.Values[0]); property.Values = new List {avaloniaPropertyNode}; propType = avaloniaPropertyNode.AvaloniaPropertyType; } From 6820d019d947870699346f78a0ace186d551cf97 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Mon, 25 Jul 2022 11:12:40 +0200 Subject: [PATCH 02/61] Fix merge --- .../Transformers/AvaloniaXamlIlSetterTransformer.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) 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 d6251badd5..ddfba5eab2 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -27,18 +27,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (styleParent != null) { - var selectorProperty = styleParent.Children.OfType() - .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Selector"); - - if (selectorProperty == null) - throw new XamlParseException( - "Can not find parent Style Selector", node); - - var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode; - - targetType = selector?.TargetType + targetType = styleParent.TargetType.GetClrType() ?? throw new XamlParseException("Can not resolve parent Style Selector type", node); - lineInfo = selector; + lineInfo = on; } else { From 04b7a8b0c19bf1d63ee3bb9ee842f61d0c56c0b5 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 26 Jul 2022 11:11:39 +0200 Subject: [PATCH 03/61] Add UT --- .../SetterTests.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs new file mode 100644 index 0000000000..229fa3aa7d --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs @@ -0,0 +1,29 @@ +using System.Linq; +using Avalonia.Data; +using Avalonia.Styling; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests +{ + public class SetterTests : XamlTestBase + { + [Fact] + public void Setter_Should_Work_Outside_Of_Style_With_SetterTargetType_Attribute() + { + using (UnitTestApplication.Start(TestServices.MockPlatformWrapper)) + { + var xaml = @" + + + + +"; + var animation = (Animation.Animation)AvaloniaRuntimeXamlLoader.Load(xaml); + var setter = (Setter)animation.Children[0].Setters[0]; + + Assert.IsType(setter.Value); + } + } + } +} From cfad3051aa49fdd15bc0a433732e3fa3aa0f5313 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 26 Jul 2022 11:12:14 +0200 Subject: [PATCH 04/61] RemoveChild for now --- .../Transformers/AvaloniaXamlIlSetterTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ddfba5eab2..d69e86e409 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -41,7 +41,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "SetterTargetType") { - //p.Children.RemoveAt(index); + p.Children.RemoveAt(index); targetType = context.Configuration.TypeSystem.GetType(((XamlAstTextNode)d.Values[0]).Text); lineInfo = d; From f6b50e5fde80dd0fb172f07e958dd4739975be0e Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Wed, 27 Jul 2022 11:10:39 +0200 Subject: [PATCH 05/61] Use StyledWindow services --- tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs index 229fa3aa7d..357245b826 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs @@ -11,7 +11,7 @@ namespace Avalonia.Markup.Xaml.UnitTests [Fact] public void Setter_Should_Work_Outside_Of_Style_With_SetterTargetType_Attribute() { - using (UnitTestApplication.Start(TestServices.MockPlatformWrapper)) + using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" From fdf852a60cfb8baf4aa19cd855220a54f520886b Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Wed, 27 Jul 2022 11:12:13 +0200 Subject: [PATCH 06/61] Don't remove node --- .../Transformers/AvaloniaXamlIlSetterTransformer.cs | 2 -- 1 file changed, 2 deletions(-) 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 d69e86e409..8c96ed96a9 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -41,8 +41,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "SetterTargetType") { - p.Children.RemoveAt(index); - targetType = context.Configuration.TypeSystem.GetType(((XamlAstTextNode)d.Values[0]).Text); lineInfo = d; From 8cae9556888dcc1469de061c0a554cb906a1eb6c Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 29 Jul 2022 11:16:28 +0200 Subject: [PATCH 07/61] Add another keyframe to UT --- tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs index 357245b826..cc1dce4de8 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs @@ -18,6 +18,9 @@ namespace Avalonia.Markup.Xaml.UnitTests + + + "; var animation = (Animation.Animation)AvaloniaRuntimeXamlLoader.Load(xaml); var setter = (Setter)animation.Children[0].Setters[0]; From 30fd91121e638e0a835a305993793b4e709db51c Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 14:43:02 -0400 Subject: [PATCH 08/61] Comment and update the Screen class --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 +- src/Avalonia.Controls/Platform/Screen.cs | 25 ++++++++++++++++++++-- src/Avalonia.Controls/Screens.cs | 16 +++++++++++++- src/Avalonia.X11/X11Screens.cs | 8 +++---- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 5 files changed, 44 insertions(+), 9 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 823f59e030..6a4ed5a890 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -65,7 +65,7 @@ namespace ControlCatalog.Pages formattedText = CreateFormattedText($"Scaling: {screen.PixelDensity * 100}%"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); - formattedText = CreateFormattedText($"Primary: {screen.Primary}"); + formattedText = CreateFormattedText($"Primary: {screen.IsPrimary}"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60)); formattedText = diff --git a/src/Avalonia.Controls/Platform/Screen.cs b/src/Avalonia.Controls/Platform/Screen.cs index 976faed3fd..0f287fd5da 100644 --- a/src/Avalonia.Controls/Platform/Screen.cs +++ b/src/Avalonia.Controls/Platform/Screen.cs @@ -1,21 +1,42 @@ namespace Avalonia.Platform { + /// + /// Represents a single display screen. + /// public class Screen { + /// + /// Gets the pixel density of the screen. + /// This is a scaling factor so multiply by 100 to get a percentage. + /// + /// + /// Both X and Y density are assumed uniform. + /// public double PixelDensity { get; } + /// + /// Gets the overall pixel-size of the screen. + /// This generally is the raw pixel counts in both the X and Y direction. + /// public PixelRect Bounds { get; } + /// + /// Gets the actual working-area pixel-size of the screen. + /// This may be smaller to account for notches and other block-out areas. + /// public PixelRect WorkingArea { get; } - public bool Primary { get; } + /// + /// Gets a value indicating whether the screen is the primary one. + /// + public bool IsPrimary { get; } public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary) { this.PixelDensity = pixelDensity; this.Bounds = bounds; this.WorkingArea = workingArea; - this.Primary = primary; + this.IsPrimary = primary; } } } diff --git a/src/Avalonia.Controls/Screens.cs b/src/Avalonia.Controls/Screens.cs index a554f82f61..da37959402 100644 --- a/src/Avalonia.Controls/Screens.cs +++ b/src/Avalonia.Controls/Screens.cs @@ -8,13 +8,27 @@ using Avalonia.VisualTree; namespace Avalonia.Controls { + /// + /// Represents all screens available on a device. + /// public class Screens { private readonly IScreenImpl _iScreenImpl; + /// + /// Gets the total number of screens available on this device. + /// public int ScreenCount => _iScreenImpl?.ScreenCount ?? 0; + + /// + /// Gets the list of all screens available on this device. + /// public IReadOnlyList All => _iScreenImpl?.AllScreens ?? Array.Empty(); - public Screen? Primary => All.FirstOrDefault(x => x.Primary); + + /// + /// Gets the primary screen on this device. + /// + public Screen? Primary => All.FirstOrDefault(x => x.IsPrimary); public Screens(IScreenImpl iScreenImpl) { diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs index bcaafb6a53..a65f09ee63 100644 --- a/src/Avalonia.X11/X11Screens.cs +++ b/src/Avalonia.X11/X11Screens.cs @@ -9,7 +9,7 @@ using JetBrains.Annotations; namespace Avalonia.X11 { - class X11Screens : IScreenImpl + class X11Screens : IScreenImpl { private IX11Screens _impl; @@ -218,7 +218,7 @@ namespace Avalonia.X11 public int ScreenCount => _impl.Screens.Length; public IReadOnlyList AllScreens => - _impl.Screens.Select(s => new Screen(s.PixelDensity, s.Bounds, s.WorkingArea, s.Primary)).ToArray(); + _impl.Screens.Select(s => new Screen(s.PixelDensity, s.Bounds, s.WorkingArea, s.IsPrimary)).ToArray(); } interface IX11Screens @@ -281,7 +281,7 @@ namespace Avalonia.X11 { private const int FullHDWidth = 1920; private const int FullHDHeight = 1080; - public bool Primary { get; } + public bool IsPrimary { get; } public string Name { get; set; } public PixelRect Bounds { get; set; } public Size? PhysicalSize { get; set; } @@ -291,7 +291,7 @@ namespace Avalonia.X11 public X11Screen(PixelRect bounds, bool primary, string name, Size? physicalSize, double? pixelDensity) { - Primary = primary; + IsPrimary = primary; Name = name; Bounds = bounds; if (physicalSize == null && pixelDensity == null) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 0f243fcf9f..9741f3f804 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -224,7 +224,7 @@ namespace Avalonia.Win32 } } - private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.Primary)?.PixelDensity ?? 1; + private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.PixelDensity ?? 1; public double RenderScaling => _scaling; From 31a15075ed6947e6f7998279d6daf59438304728 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 14:47:26 -0400 Subject: [PATCH 09/61] Rename missed parameter --- src/Avalonia.Controls/Platform/Screen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Platform/Screen.cs b/src/Avalonia.Controls/Platform/Screen.cs index 0f287fd5da..cdab81705f 100644 --- a/src/Avalonia.Controls/Platform/Screen.cs +++ b/src/Avalonia.Controls/Platform/Screen.cs @@ -31,12 +31,12 @@ /// public bool IsPrimary { get; } - public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary) + public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool isPrimary) { this.PixelDensity = pixelDensity; this.Bounds = bounds; this.WorkingArea = workingArea; - this.IsPrimary = primary; + this.IsPrimary = isPrimary; } } } From 7c8982ae3f7db992f8482010e7c87b0974f75d44 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 10 Oct 2022 11:55:56 +0000 Subject: [PATCH 10/61] prevent selection from being updated on focus when toggle is enabled --- src/Avalonia.Controls/ListBox.cs | 3 +- .../Primitives/SelectingItemsControl.cs | 45 +++++++++++++------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index 80b5259a53..86118d7b00 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -139,7 +139,8 @@ namespace Avalonia.Controls e.Source, true, e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), - e.KeyModifiers.HasAllFlags(KeyModifiers.Control)); + e.KeyModifiers.HasAllFlags(KeyModifiers.Control), + fromFocus: true); } } diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index ea20247b4b..e03b02a479 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -586,6 +586,14 @@ namespace Avalonia.Controls.Primitives Selection.SelectAll(); e.Handled = true; } + else if (e.Key == Key.Space || e.Key == Key.Enter) + { + e.Handled = UpdateSelectionFromEventSource( + e.Source, + true, + e.KeyModifiers.HasFlag(KeyModifiers.Shift), + e.KeyModifiers.HasFlag(KeyModifiers.Control)); + } } } @@ -662,12 +670,14 @@ namespace Avalonia.Controls.Primitives /// Whether the range modifier is enabled (i.e. shift key). /// Whether the toggle modifier is enabled (i.e. ctrl key). /// Whether the event is a right-click. + /// Wheter the event is a focus event protected void UpdateSelection( int index, bool select = true, bool rangeModifier = false, bool toggleModifier = false, - bool rightButton = false) + bool rightButton = false, + bool fromFocus = false) { if (index < 0 || index >= ItemCount) { @@ -696,22 +706,25 @@ namespace Avalonia.Controls.Primitives Selection.Clear(); Selection.SelectRange(Selection.AnchorIndex, index); } - else if (multi && toggle) + else if (!fromFocus && toggle) { - if (Selection.IsSelected(index) == true) + if (multi) { - Selection.Deselect(index); + if (Selection.IsSelected(index) == true) + { + Selection.Deselect(index); + } + else + { + Selection.Select(index); + } } else { - Selection.Select(index); + SelectedIndex = (SelectedIndex == index) ? -1 : index; } } - else if (toggle) - { - SelectedIndex = (SelectedIndex == index) ? -1 : index; - } - else + else if (!toggle) { using var operation = Selection.BatchUpdate(); Selection.Clear(); @@ -735,18 +748,20 @@ namespace Avalonia.Controls.Primitives /// Whether the range modifier is enabled (i.e. shift key). /// Whether the toggle modifier is enabled (i.e. ctrl key). /// Whether the event is a right-click. + /// Wheter the event is a focus event protected void UpdateSelection( IControl container, bool select = true, bool rangeModifier = false, bool toggleModifier = false, - bool rightButton = false) + bool rightButton = false, + bool fromFocus = false) { var index = ItemContainerGenerator?.IndexFromContainer(container) ?? -1; if (index != -1) { - UpdateSelection(index, select, rangeModifier, toggleModifier, rightButton); + UpdateSelection(index, select, rangeModifier, toggleModifier, rightButton, fromFocus); } } @@ -759,6 +774,7 @@ namespace Avalonia.Controls.Primitives /// Whether the range modifier is enabled (i.e. shift key). /// Whether the toggle modifier is enabled (i.e. ctrl key). /// Whether the event is a right-click. + /// Wheter the event is a focus event /// /// True if the event originated from a container that belongs to the control; otherwise /// false. @@ -768,13 +784,14 @@ namespace Avalonia.Controls.Primitives bool select = true, bool rangeModifier = false, bool toggleModifier = false, - bool rightButton = false) + bool rightButton = false, + bool fromFocus = false) { var container = GetContainerFromEventSource(eventSource); if (container != null) { - UpdateSelection(container, select, rangeModifier, toggleModifier, rightButton); + UpdateSelection(container, select, rangeModifier, toggleModifier, rightButton, fromFocus); return true; } From 89f031574ba9ca9cdc231242a87c4b852e4070b7 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 10 Oct 2022 13:19:25 +0000 Subject: [PATCH 11/61] Add tests --- .../ListBoxTests_Multiple.cs | 8 +-- .../ListBoxTests_Single.cs | 52 +++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs index 7c7cdd08db..556959effb 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs @@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Focusing_Item_With_Ctrl_And_Arrow_Key_Should_Add_To_Selection() + public void Focusing_Item_With_Ctrl_And_Arrow_Key_Should_Not_Add_To_Selection() { var target = new ListBox { @@ -56,11 +56,11 @@ namespace Avalonia.Controls.UnitTests KeyModifiers = KeyModifiers.Control }); - Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); + Assert.Equal(new[] { "Foo" }, target.SelectedItems); } [Fact] - public void Focusing_Selected_Item_With_Ctrl_And_Arrow_Key_Should_Remove_From_Selection() + public void Focusing_Selected_Item_With_Ctrl_And_Arrow_Key_Should_Not_Remove_From_Selection() { var target = new ListBox { @@ -81,7 +81,7 @@ namespace Avalonia.Controls.UnitTests KeyModifiers = KeyModifiers.Control }); - Assert.Equal(new[] { "Bar" }, target.SelectedItems); + Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); } private Control CreateListBoxTemplate(ITemplatedControl parent, INameScope scope) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index bf516748cc..726a39ac46 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -59,6 +59,58 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(0, target.SelectedIndex); } + [Fact] + public void Focusing_Item_With_Arrow_Key_And_Ctrl_Pressed_Should_Not_Select_It() + { + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + + ApplyTemplate(target); + + target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs + { + RoutedEvent = InputElement.GotFocusEvent, + NavigationMethod = NavigationMethod.Directional, + KeyModifiers = KeyModifiers.Control + }); + + Assert.Equal(-1, target.SelectedIndex); + } + + [Fact] + public void Pressing_Space_On_Focused_Item_With_Ctrl_Pressed_Should_Select_It() + { + using (UnitTestApplication.Start()) + { + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + + target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs + { + RoutedEvent = InputElement.GotFocusEvent, + NavigationMethod = NavigationMethod.Directional, + KeyModifiers = KeyModifiers.Control + }); + + target.Presenter.Panel.Children[0].RaiseEvent(new KeyEventArgs + { + RoutedEvent = InputElement.KeyDownEvent, + Key = Key.Space, + KeyModifiers = KeyModifiers.Control + }); + + Assert.Equal(0, target.SelectedIndex); + } + } + [Fact] public void Clicking_Item_Should_Select_It() { From f91a788d18a40c187cd09e3516d86619869429c4 Mon Sep 17 00:00:00 2001 From: dif-sam <41672086+dif-sam@users.noreply.github.com> Date: Thu, 13 Oct 2022 16:21:54 +0400 Subject: [PATCH 12/61] Suggested fix for #9173 trying to deffere request for BringIntoView, when TreeViewItem not ready yet. --- src/Avalonia.Controls/TreeViewItem.cs | 29 +++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index ada081b808..c2304abbc8 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -45,6 +45,8 @@ namespace Avalonia.Controls private IControl? _header; private bool _isExpanded; private int _level; + private bool _templateApplied; + private bool _deferredBringIntoViewFlag; /// /// Initializes static members of the class. @@ -136,15 +138,24 @@ namespace Avalonia.Controls protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e) { - if (e.TargetObject == this && _header != null) + if (e.TargetObject == this) { - var m = _header.TransformToVisual(this); + if (!_templateApplied) + { + _deferredBringIntoViewFlag = true; + return; + } - if (m.HasValue) + if (_header != null) { - var bounds = new Rect(_header.Bounds.Size); - var rect = bounds.TransformToAABB(m.Value); - e.TargetRect = rect; + var m = _header.TransformToVisual(this); + + if (m.HasValue) + { + var bounds = new Rect(_header.Bounds.Size); + var rect = bounds.TransformToAABB(m.Value); + e.TargetRect = rect; + } } } } @@ -187,6 +198,12 @@ namespace Avalonia.Controls protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _header = e.NameScope.Find("PART_Header"); + _templateApplied = true; + if (_deferredBringIntoViewFlag) + { + _deferredBringIntoViewFlag = false; + Dispatcher.UIThread.Post(this.BringIntoView); // must use the Dispatcher, otherwise the TreeView doesn't scroll + } } private static int CalculateDistanceFromLogicalParent(ILogical? logical, int @default = -1) where T : class From 65d39603fac9f963324158ab8d7c420565c5785b Mon Sep 17 00:00:00 2001 From: dif-sam <41672086+dif-sam@users.noreply.github.com> Date: Thu, 13 Oct 2022 16:55:55 +0400 Subject: [PATCH 13/61] Forgotten namespase added to usings --- src/Avalonia.Controls/TreeViewItem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index c2304abbc8..2f96e6911f 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.LogicalTree; +using Avalonia.Threading; namespace Avalonia.Controls { From cc915f888cca167e32157f66a81654c8959e1374 Mon Sep 17 00:00:00 2001 From: Robin Krom Date: Sun, 16 Oct 2022 16:31:00 +0200 Subject: [PATCH 14/61] fix: Under Windows - Arithmetic operation resulted in an overflow. --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 9d1920498b..f28f4fd740 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1877,7 +1877,7 @@ namespace Avalonia.Win32.Interop public static uint LGID(IntPtr HKL) { - return (uint)(HKL.ToInt32() & 0xffff); + return (uint)(HKL.ToInt64() & 0xffff); } public const int SORT_DEFAULT = 0; From 6bc689a662cd9f8154fed77fd6aa13e494cbb3ad Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 18 Oct 2022 21:10:59 -0400 Subject: [PATCH 15/61] Updates from code review --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 +- src/Avalonia.Controls/Platform/Screen.cs | 35 +++++++++++++++------- src/Avalonia.Native/WindowImplBase.cs | 2 +- src/Avalonia.X11/X11Window.cs | 2 +- src/Windows/Avalonia.Win32/TrayIconImpl.cs | 8 ++--- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 6a4ed5a890..119bc09888 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -62,7 +62,7 @@ namespace ControlCatalog.Pages CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20)); - formattedText = CreateFormattedText($"Scaling: {screen.PixelDensity * 100}%"); + formattedText = CreateFormattedText($"Scaling: {screen.Scale * 100}%"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); formattedText = CreateFormattedText($"Primary: {screen.IsPrimary}"); diff --git a/src/Avalonia.Controls/Platform/Screen.cs b/src/Avalonia.Controls/Platform/Screen.cs index cdab81705f..e5b8ca5555 100644 --- a/src/Avalonia.Controls/Platform/Screen.cs +++ b/src/Avalonia.Controls/Platform/Screen.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Platform +using System; + +namespace Avalonia.Platform { /// /// Represents a single display screen. @@ -6,34 +8,47 @@ public class Screen { /// - /// Gets the pixel density of the screen. - /// This is a scaling factor so multiply by 100 to get a percentage. + /// Gets the scaling factor applied to the screen by the operating system. /// /// - /// Both X and Y density are assumed uniform. + /// Multiply this value by 100 to get a percentage. + /// Both X and Y scaling factors are assumed uniform. /// - public double PixelDensity { get; } + public double Scale { get; } + + /// + [Obsolete("Use the Scale property instead.")] + public double PixelDensity => Scale; /// /// Gets the overall pixel-size of the screen. - /// This generally is the raw pixel counts in both the X and Y direction. /// + /// + /// This generally is the raw pixel counts in both the X and Y direction. + /// public PixelRect Bounds { get; } /// /// Gets the actual working-area pixel-size of the screen. - /// This may be smaller to account for notches and other block-out areas. /// + /// + /// This area may be smaller than to account for notches and + /// other block-out areas such as taskbars etc. + /// public PixelRect WorkingArea { get; } /// /// Gets a value indicating whether the screen is the primary one. /// public bool IsPrimary { get; } - - public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool isPrimary) + + /// + [Obsolete("Use the IsPrimary property instead.")] + public bool Primary => IsPrimary; + + public Screen(double scale, PixelRect bounds, PixelRect workingArea, bool isPrimary) { - this.PixelDensity = pixelDensity; + this.Scale = scale; this.Bounds = bounds; this.WorkingArea = workingArea; this.IsPrimary = isPrimary; diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 34de439c94..8bb2b0a713 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -92,7 +92,7 @@ namespace Avalonia.Native _savedScaling = RenderScaling; _nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost()); - var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) + var monitor = Screen.AllScreens.OrderBy(x => x.Scale) .FirstOrDefault(m => m.Bounds.Contains(Position)); Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d), PlatformResizeReason.Layout); diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index f24c33cafa..75b741ac77 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -120,7 +120,7 @@ namespace Avalonia.X11 if (!_popup && Screen != null) { - var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) + var monitor = Screen.AllScreens.OrderBy(x => x.Scale) .FirstOrDefault(m => m.Bounds.Contains(Position)); if (monitor != null) diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index 346d6e5adb..93bdfda652 100644 --- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs +++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs @@ -216,7 +216,7 @@ namespace Avalonia.Win32 { Anchor = PopupAnchor.TopLeft, Gravity = PopupGravity.BottomRight, - AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.PixelDensity, new Size(1, 1)), + AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.Scale, new Size(1, 1)), Size = finalRect.Size, ConstraintAdjustment = PopupPositionerConstraintAdjustment.FlipX | PopupPositionerConstraintAdjustment.FlipY, }); @@ -244,16 +244,16 @@ namespace Avalonia.Win32 { var point = _hiddenWindow.Screens.Primary.Bounds.TopLeft; var size = _hiddenWindow.Screens.Primary.Bounds.Size; - return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.PixelDensity, size.Height * _hiddenWindow.Screens.Primary.PixelDensity); + return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.Scale, size.Height * _hiddenWindow.Screens.Primary.Scale); } } public void MoveAndResize(Point devicePoint, Size virtualSize) { - _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.PixelDensity); + _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.Scale); } - public double Scaling => _hiddenWindow.Screens.Primary.PixelDensity; + public double Scaling => _hiddenWindow.Screens.Primary.Scale; } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9741f3f804..cd8515eaa6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -224,7 +224,7 @@ namespace Avalonia.Win32 } } - private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.PixelDensity ?? 1; + private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.Scale ?? 1; public double RenderScaling => _scaling; From f271654d64885ffae707bf00e7b787a1ec39c62a Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 19 Oct 2022 09:49:06 +0200 Subject: [PATCH 16/61] fix: CS8350 --- src/Avalonia.Base/Media/PathMarkupParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Media/PathMarkupParser.cs b/src/Avalonia.Base/Media/PathMarkupParser.cs index 30c5206125..cf12bf5126 100644 --- a/src/Avalonia.Base/Media/PathMarkupParser.cs +++ b/src/Avalonia.Base/Media/PathMarkupParser.cs @@ -188,7 +188,7 @@ namespace Avalonia.Media _isOpen = true; } - private void SetFillRule(ref ReadOnlySpan span) + private void SetFillRule(scoped ref ReadOnlySpan span) { ThrowIfDisposed(); @@ -452,7 +452,7 @@ namespace Avalonia.Media return !span.IsEmpty && (span[0] == ',' || span[0] == '-' || span[0] == '.' || char.IsDigit(span[0])); } - private static bool ReadArgument(ref ReadOnlySpan remaining, out ReadOnlySpan argument) + private static bool ReadArgument(scoped ref ReadOnlySpan remaining, out ReadOnlySpan argument) { remaining = SkipWhitespace(remaining); if (remaining.IsEmpty) From 89faa52240e1fb3b1f537d09bc5bcec050dfe983 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 19 Oct 2022 16:04:09 +0200 Subject: [PATCH 17/61] Use DragEventArgs for DragLeave event. This follows the WPF/UWP API and allows pointer information to be retrieved. --- src/Avalonia.Base/Input/DragDrop.cs | 2 +- src/Avalonia.Base/Input/DragDropDevice.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Input/DragDrop.cs b/src/Avalonia.Base/Input/DragDrop.cs index 723d577964..fe75f678e1 100644 --- a/src/Avalonia.Base/Input/DragDrop.cs +++ b/src/Avalonia.Base/Input/DragDrop.cs @@ -13,7 +13,7 @@ namespace Avalonia.Input /// /// Event which is raised, when a drag-and-drop operation leaves the element. /// - public static readonly RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); /// /// Event which is raised, when a drag-and-drop operation is updated while over the element. /// diff --git a/src/Avalonia.Base/Input/DragDropDevice.cs b/src/Avalonia.Base/Input/DragDropDevice.cs index 30a08eda17..3c91856dcd 100644 --- a/src/Avalonia.Base/Input/DragDropDevice.cs +++ b/src/Avalonia.Base/Input/DragDropDevice.cs @@ -54,7 +54,7 @@ namespace Avalonia.Input try { if (_lastTarget != null) - _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent)); + RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragLeaveEvent, effects, data, modifiers); return RaiseDragEvent(target, inputRoot, point, DragDrop.DragEnterEvent, effects, data, modifiers); } finally @@ -63,13 +63,13 @@ namespace Avalonia.Input } } - private void DragLeave(IInputElement inputRoot) + private void DragLeave(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers) { if (_lastTarget == null) return; try { - _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent)); + RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragLeaveEvent, effects, data, modifiers); } finally { @@ -106,7 +106,7 @@ namespace Avalonia.Input e.Effects = DragOver(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers); break; case RawDragEventType.DragLeave: - DragLeave(e.Root); + DragLeave(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers); break; case RawDragEventType.Drop: e.Effects = Drop(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers); From a88fdd56b97ad1b2aa9907b03546b1da292dc8cf Mon Sep 17 00:00:00 2001 From: Benedikt Date: Wed, 19 Oct 2022 16:16:12 +0200 Subject: [PATCH 18/61] Adjust Microsoft.CodeAnalysis package version --- src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index b16ee84b4d..fe694b5730 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -19,7 +19,8 @@ - + + From 10b584d95c7dd903f18f706e1f64cdcaffbd18a2 Mon Sep 17 00:00:00 2001 From: Robin Krom Date: Wed, 19 Oct 2022 22:14:48 +0200 Subject: [PATCH 19/61] After the fix still causing issues, I figured the value must be >0xffff_ffff so not fitting. I changed to logic to handle that case too. --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index f28f4fd740..3bcc395abe 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1877,7 +1877,10 @@ namespace Avalonia.Win32.Interop public static uint LGID(IntPtr HKL) { - return (uint)(HKL.ToInt64() & 0xffff); + unchecked + { + return (uint)((ulong)HKL & 0xffff); + } } public const int SORT_DEFAULT = 0; From d816726ef000545ea547bd1e24ea58be56bdb7c9 Mon Sep 17 00:00:00 2001 From: robloo Date: Wed, 19 Oct 2022 21:19:13 -0400 Subject: [PATCH 20/61] Rename Scale to Scaling and update more comments --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 +- src/Avalonia.Controls/Platform/IScreenImpl.cs | 6 +++++ src/Avalonia.Controls/Platform/Screen.cs | 19 ++++++++++----- src/Avalonia.Controls/Screens.cs | 13 ++++++---- src/Avalonia.Native/ScreenImpl.cs | 4 ++-- src/Avalonia.Native/WindowImplBase.cs | 2 +- src/Avalonia.Native/avn.idl | 4 ++-- src/Avalonia.X11/X11Screens.cs | 24 +++++++++++-------- src/Avalonia.X11/X11Window.cs | 8 +++---- src/Windows/Avalonia.Win32/TrayIconImpl.cs | 8 +++---- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 11 files changed, 56 insertions(+), 36 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 119bc09888..ab75e4a4e2 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -65,7 +65,7 @@ namespace ControlCatalog.Pages formattedText = CreateFormattedText($"Scaling: {screen.Scale * 100}%"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); - formattedText = CreateFormattedText($"Primary: {screen.IsPrimary}"); + formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60)); formattedText = diff --git a/src/Avalonia.Controls/Platform/IScreenImpl.cs b/src/Avalonia.Controls/Platform/IScreenImpl.cs index fcae3b6493..e2a0b3e3f3 100644 --- a/src/Avalonia.Controls/Platform/IScreenImpl.cs +++ b/src/Avalonia.Controls/Platform/IScreenImpl.cs @@ -6,8 +6,14 @@ namespace Avalonia.Platform [Unstable] public interface IScreenImpl { + /// + /// Gets the total number of screens available on the device. + /// int ScreenCount { get; } + /// + /// Gets the list of all screens available on the device. + /// IReadOnlyList AllScreens { get; } Screen? ScreenFromWindow(IWindowBaseImpl window); diff --git a/src/Avalonia.Controls/Platform/Screen.cs b/src/Avalonia.Controls/Platform/Screen.cs index e5b8ca5555..4898c5f912 100644 --- a/src/Avalonia.Controls/Platform/Screen.cs +++ b/src/Avalonia.Controls/Platform/Screen.cs @@ -14,11 +14,11 @@ namespace Avalonia.Platform /// Multiply this value by 100 to get a percentage. /// Both X and Y scaling factors are assumed uniform. /// - public double Scale { get; } + public double Scaling { get; } - /// - [Obsolete("Use the Scale property instead.")] - public double PixelDensity => Scale; + /// + [Obsolete("Use the Scaling property instead.")] + public double PixelDensity => Scaling; /// /// Gets the overall pixel-size of the screen. @@ -46,9 +46,16 @@ namespace Avalonia.Platform [Obsolete("Use the IsPrimary property instead.")] public bool Primary => IsPrimary; - public Screen(double scale, PixelRect bounds, PixelRect workingArea, bool isPrimary) + /// + /// Initializes a new instance of the class. + /// + /// The scaling factor applied to the screen by the operating system. + /// The overall pixel-size of the screen. + /// The actual working-area pixel-size of the screen. + /// Whether the screen is the primary one. + public Screen(double scaling, PixelRect bounds, PixelRect workingArea, bool isPrimary) { - this.Scale = scale; + this.Scaling = scaling; this.Bounds = bounds; this.WorkingArea = workingArea; this.IsPrimary = isPrimary; diff --git a/src/Avalonia.Controls/Screens.cs b/src/Avalonia.Controls/Screens.cs index da37959402..dde6b71e6e 100644 --- a/src/Avalonia.Controls/Screens.cs +++ b/src/Avalonia.Controls/Screens.cs @@ -16,20 +16,23 @@ namespace Avalonia.Controls private readonly IScreenImpl _iScreenImpl; /// - /// Gets the total number of screens available on this device. + /// Gets the total number of screens available on the device. /// public int ScreenCount => _iScreenImpl?.ScreenCount ?? 0; /// - /// Gets the list of all screens available on this device. + /// Gets the list of all screens available on the device. /// public IReadOnlyList All => _iScreenImpl?.AllScreens ?? Array.Empty(); /// - /// Gets the primary screen on this device. + /// Gets the primary screen on the device. /// public Screen? Primary => All.FirstOrDefault(x => x.IsPrimary); + /// + /// Initializes a new instance of the class. + /// public Screens(IScreenImpl iScreenImpl) { _iScreenImpl = iScreenImpl; @@ -39,14 +42,14 @@ namespace Avalonia.Controls { return _iScreenImpl.ScreenFromRect(bounds); } - + public Screen? ScreenFromWindow(IWindowBaseImpl window) { return _iScreenImpl.ScreenFromWindow(window); } public Screen? ScreenFromPoint(PixelPoint point) - { + { return _iScreenImpl.ScreenFromPoint(point); } diff --git a/src/Avalonia.Native/ScreenImpl.cs b/src/Avalonia.Native/ScreenImpl.cs index 83db2e8a28..53bd12cde1 100644 --- a/src/Avalonia.Native/ScreenImpl.cs +++ b/src/Avalonia.Native/ScreenImpl.cs @@ -30,10 +30,10 @@ namespace Avalonia.Native var screen = _native.GetScreen(i); result[i] = new Screen( - screen.PixelDensity, + screen.Scaling, screen.Bounds.ToAvaloniaPixelRect(), screen.WorkingArea.ToAvaloniaPixelRect(), - screen.Primary.FromComBool()); + screen.IsPrimary.FromComBool()); } return result; diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 8bb2b0a713..381741cea9 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -92,7 +92,7 @@ namespace Avalonia.Native _savedScaling = RenderScaling; _nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost()); - var monitor = Screen.AllScreens.OrderBy(x => x.Scale) + var monitor = Screen.AllScreens.OrderBy(x => x.Scaling) .FirstOrDefault(m => m.Bounds.Contains(Position)); Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d), PlatformResizeReason.Layout); diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index a98d213887..b0bff07146 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -256,8 +256,8 @@ struct AvnScreen { AvnRect Bounds; AvnRect WorkingArea; - float PixelDensity; - bool Primary; + float Scaling; + bool IsPrimary; } enum AvnPixelFormat diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs index a65f09ee63..ba6029b350 100644 --- a/src/Avalonia.X11/X11Screens.cs +++ b/src/Avalonia.X11/X11Screens.cs @@ -218,7 +218,7 @@ namespace Avalonia.X11 public int ScreenCount => _impl.Screens.Length; public IReadOnlyList AllScreens => - _impl.Screens.Select(s => new Screen(s.PixelDensity, s.Bounds, s.WorkingArea, s.IsPrimary)).ToArray(); + _impl.Screens.Select(s => new Screen(s.Scaling, s.Bounds, s.WorkingArea, s.IsPrimary)).ToArray(); } interface IX11Screens @@ -285,26 +285,30 @@ namespace Avalonia.X11 public string Name { get; set; } public PixelRect Bounds { get; set; } public Size? PhysicalSize { get; set; } - public double PixelDensity { get; set; } + public double Scaling { get; set; } public PixelRect WorkingArea { get; set; } - public X11Screen(PixelRect bounds, bool primary, - string name, Size? physicalSize, double? pixelDensity) + public X11Screen( + PixelRect bounds, + bool isPrimary, + string name, + Size? physicalSize, + double? scaling) { - IsPrimary = primary; + IsPrimary = isPrimary; Name = name; Bounds = bounds; - if (physicalSize == null && pixelDensity == null) + if (physicalSize == null && scaling == null) { - PixelDensity = 1; + Scaling = 1; } - else if (pixelDensity == null) + else if (scaling == null) { - PixelDensity = GuessPixelDensity(bounds, physicalSize.Value); + Scaling = GuessPixelDensity(bounds, physicalSize.Value); } else { - PixelDensity = pixelDensity.Value; + Scaling = scaling.Value; PhysicalSize = physicalSize; } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 75b741ac77..690ac0ebce 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -120,7 +120,7 @@ namespace Avalonia.X11 if (!_popup && Screen != null) { - var monitor = Screen.AllScreens.OrderBy(x => x.Scale) + var monitor = Screen.AllScreens.OrderBy(x => x.Scaling) .FirstOrDefault(m => m.Bounds.Contains(Position)); if (monitor != null) @@ -570,9 +570,9 @@ namespace Avalonia.X11 newScaling = _scalingOverride.Value; else { - var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity) + var monitor = _platform.X11Screens.Screens.OrderBy(x => x.Scaling) .FirstOrDefault(m => m.Bounds.Contains(Position)); - newScaling = monitor?.PixelDensity ?? RenderScaling; + newScaling = monitor?.Scaling ?? RenderScaling; } if (RenderScaling != newScaling) @@ -994,7 +994,7 @@ namespace Avalonia.X11 public IScreenImpl Screen => _platform.Screens; - public Size MaxAutoSizeHint => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity)) + public Size MaxAutoSizeHint => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.Scaling)) .OrderByDescending(x => x.Width + x.Height).FirstOrDefault(); diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index 93bdfda652..8d565d7fef 100644 --- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs +++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs @@ -216,7 +216,7 @@ namespace Avalonia.Win32 { Anchor = PopupAnchor.TopLeft, Gravity = PopupGravity.BottomRight, - AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.Scale, new Size(1, 1)), + AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.Scaling, new Size(1, 1)), Size = finalRect.Size, ConstraintAdjustment = PopupPositionerConstraintAdjustment.FlipX | PopupPositionerConstraintAdjustment.FlipY, }); @@ -244,16 +244,16 @@ namespace Avalonia.Win32 { var point = _hiddenWindow.Screens.Primary.Bounds.TopLeft; var size = _hiddenWindow.Screens.Primary.Bounds.Size; - return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.Scale, size.Height * _hiddenWindow.Screens.Primary.Scale); + return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.Scaling, size.Height * _hiddenWindow.Screens.Primary.Scaling); } } public void MoveAndResize(Point devicePoint, Size virtualSize) { - _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.Scale); + _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.Scaling); } - public double Scaling => _hiddenWindow.Screens.Primary.Scale; + public double Scaling => _hiddenWindow.Screens.Primary.Scaling; } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index cd8515eaa6..5374614379 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -224,7 +224,7 @@ namespace Avalonia.Win32 } } - private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.Scale ?? 1; + private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.Scaling ?? 1; public double RenderScaling => _scaling; From 1cf220016f7bd63447b6447f31cd82256d0f548a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 20 Oct 2022 13:31:16 +0100 Subject: [PATCH 21/61] focus the root container on page load. --- src/Web/Avalonia.Web/AvaloniaView.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index 3a31679424..12d31258b5 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -136,6 +136,8 @@ namespace Avalonia.Web DomHelper.ObserveSize(host, null, OnSizeChanged); CanvasHelper.RequestAnimationFrame(_canvas, true); + + InputHelper.FocusElement(_containerElement); } private static RawPointerPoint ExtractRawPointerFromJSArgs(JSObject args) From 3f4034f0caf06dbdb36438666b391c0a8f62bdd6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 20 Oct 2022 21:41:15 +0100 Subject: [PATCH 22/61] add github sponsors --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index c5a719ce90..df070c35cf 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ +github: avaloniaui open_collective: avalonia From 5d8e4998c1208180395f8c2345c618c12e6bb817 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 20 Oct 2022 22:05:39 +0100 Subject: [PATCH 23/61] prevent browser touch gestures happening outside of avalonia --- src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts index 2257d56a92..783710bb3e 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts @@ -11,6 +11,7 @@ export class AvaloniaDOM { host.tabIndex = 0; host.oncontextmenu = function () { return false; }; host.style.overflow = "hidden"; + host.style.touchAction = "none"; // Rendering target canvas const canvas = document.createElement("canvas"); From 668296b397210192d72c0107135f0cc76f1bc1c2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 20 Oct 2022 22:21:49 +0100 Subject: [PATCH 24/61] minify js --- src/Web/Avalonia.Web/webapp/build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Web/Avalonia.Web/webapp/build.js b/src/Web/Avalonia.Web/webapp/build.js index 6b6df4c300..81f863cac7 100644 --- a/src/Web/Avalonia.Web/webapp/build.js +++ b/src/Web/Avalonia.Web/webapp/build.js @@ -5,7 +5,7 @@ require("esbuild").build({ ], outdir: "../wwwroot", bundle: true, - minify: false, + minify: true, format: "esm", target: "es2016", platform: "browser", From 91d25f7416981eebebe769757d944627b2f7ab8c Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 20 Oct 2022 20:12:40 -0400 Subject: [PATCH 25/61] Fix missed property rename --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index ab75e4a4e2..ff62b834c4 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -62,7 +62,7 @@ namespace ControlCatalog.Pages CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20)); - formattedText = CreateFormattedText($"Scaling: {screen.Scale * 100}%"); + formattedText = CreateFormattedText($"Scaling: {screen.Scaling * 100}%"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}"); From 9cbe262117e30a056177ebf80a87bc3c11d87d80 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 21 Oct 2022 22:05:43 +0100 Subject: [PATCH 26/61] fix copypasta --- src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts index 783710bb3e..668212964c 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts @@ -24,7 +24,7 @@ export class AvaloniaDOM { // Native controls host const nativeHost = document.createElement("div"); - canvas.id = `nativeHost${randomIdPart}`; + nativeHost.id = `nativeHost${randomIdPart}`; nativeHost.classList.add("avalonia-native-host"); nativeHost.style.left = "0px"; nativeHost.style.top = "0px"; @@ -34,7 +34,7 @@ export class AvaloniaDOM { // IME const inputElement = document.createElement("input"); - canvas.id = `input${randomIdPart}`; + inputElement.id = `inputElement${randomIdPart}`; inputElement.classList.add("avalonia-input-element"); inputElement.autocapitalize = "none"; inputElement.type = "text"; From 31cef0b54ffd22d3e565aa55d4e7e006e019e8d8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 21 Oct 2022 22:10:24 +0100 Subject: [PATCH 27/61] basic non-ime text input. --- src/Web/Avalonia.Web/AvaloniaView.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index 12d31258b5..9d7a629836 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -254,7 +254,14 @@ namespace Avalonia.Web private bool OnKeyDown (string code, string key, int modifier) { - return _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier); + var handled = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier); + + if (!handled) + { + handled = _topLevelImpl.RawTextEvent(key); + } + + return handled; } private bool OnKeyUp(string code, string key, int modifier) From e5b745694ac22c7129c28b12a29b216770a1dafd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 21 Oct 2022 22:23:05 +0100 Subject: [PATCH 28/61] filter modifier text input events. --- src/Web/Avalonia.Web/AvaloniaView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index 9d7a629836..296e411fa8 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -256,7 +256,7 @@ namespace Avalonia.Web { var handled = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier); - if (!handled) + if (!handled && key.Length == 1) { handled = _topLevelImpl.RawTextEvent(key); } From 1d19c37a37aa106c14002cbb88af701625f6fb34 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 22 Oct 2022 09:43:51 +0100 Subject: [PATCH 29/61] wasm prevent touch flashing android. --- src/Web/Avalonia.Web/AvaloniaView.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index 296e411fa8..098b06a0a2 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -77,8 +77,7 @@ namespace Avalonia.Web _topLevelImpl.SetCssCursor = (cursor) => { - InputHelper.SetCursor(_containerElement, cursor); // macOS - InputHelper.SetCursor(_canvas, cursor); // windows + InputHelper.SetCursor(_containerElement, cursor); }; _topLevel.Prepare(); From eb1c1971f1d92eb68d319cf4b3c032800f2d6600 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 22 Oct 2022 10:28:51 +0100 Subject: [PATCH 30/61] remove cursor style when we want normal cursor. --- src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts index 768414ccab..8198e09738 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts @@ -159,7 +159,12 @@ export class InputHelper { } public static setCursor(inputElement: HTMLInputElement, kind: string) { - inputElement.style.cursor = kind; + if(kind == "pointer"){ + inputElement.style.removeProperty("cursor"); + } + else { + inputElement.style.cursor = kind; + } } public static setBounds(inputElement: HTMLInputElement, x: number, y: number, caretWidth: number, caretHeight: number, caret: number) { From 116146e847ad8b3506de58ff6469a17b1ae729b9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 22 Oct 2022 12:23:57 +0100 Subject: [PATCH 31/61] smooth resizing wasm. --- .../ControlCatalog.Web/ControlCatalog.Web.csproj | 5 ++--- samples/ControlCatalog.Web/Roots.xml | 1 + .../webapp/modules/avalonia/canvas.ts | 16 ++++++++-------- .../webapp/modules/avalonia/input.ts | 5 ++--- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj index 0ddec3444b..06a5466619 100644 --- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj +++ b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj @@ -16,9 +16,8 @@ full true true - true - -O3 - -O3 + -O2 + -O2 diff --git a/samples/ControlCatalog.Web/Roots.xml b/samples/ControlCatalog.Web/Roots.xml index 3c13098159..b07fd86fa2 100644 --- a/samples/ControlCatalog.Web/Roots.xml +++ b/samples/ControlCatalog.Web/Roots.xml @@ -3,4 +3,5 @@ + diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts index 9ae9b3d2a8..8f3539b4f8 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts @@ -210,23 +210,23 @@ interface SizeWatcherInstance { export class SizeWatcher { static observer: ResizeObserver; static elements: Map; + private static lastMove: number; public static observe(element: HTMLElement, elementId: string | undefined, callback: (width: number, height: number) => void): void { if (!element || !callback) { return; } - SizeWatcher.init(); + callback(element.clientWidth, element.clientHeight); - const watcherElement = element as SizeWatcherElement; - watcherElement.SizeWatcher = { - callback + const handleResize = (args: UIEvent) => { + if (Date.now() - this.lastMove > 33) { + callback(element.clientWidth, element.clientHeight); + SizeWatcher.lastMove = Date.now(); + } }; - SizeWatcher.elements.set(elementId ?? element.id, element); - SizeWatcher.observer.observe(element); - - SizeWatcher.invoke(element); + window.addEventListener("resize", handleResize); } public static unobserve(elementId: string): void { diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts index 8198e09738..ddc1f54ae7 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts @@ -159,10 +159,9 @@ export class InputHelper { } public static setCursor(inputElement: HTMLInputElement, kind: string) { - if(kind == "pointer"){ + if (kind === "pointer") { inputElement.style.removeProperty("cursor"); - } - else { + } else { inputElement.style.cursor = kind; } } From 38717dafbbccc958089d532794819cb2517b019c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 22 Oct 2022 12:52:02 +0100 Subject: [PATCH 32/61] smooth resizing attempt 2. --- src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts index 8f3539b4f8..010c9cfacc 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts @@ -217,10 +217,12 @@ export class SizeWatcher { return; } + SizeWatcher.lastMove = Date.now(); + callback(element.clientWidth, element.clientHeight); const handleResize = (args: UIEvent) => { - if (Date.now() - this.lastMove > 33) { + if (Date.now() - SizeWatcher.lastMove > 40) { callback(element.clientWidth, element.clientHeight); SizeWatcher.lastMove = Date.now(); } From f5d3f9b2d332a5a68b316779493085ec4b064f80 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 22 Oct 2022 13:40:40 +0100 Subject: [PATCH 33/61] we dont handle gl makecurrent. --- .../webapp/modules/avalonia/canvas.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts index 010c9cfacc..62f62fe46f 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts @@ -106,12 +106,6 @@ export class Canvas { // add the draw to the next frame this.renderLoopRequest = window.requestAnimationFrame(() => { - if (this.glInfo) { - const GL = (globalThis as any).AvaloniaGL; - // make current - GL.makeContextCurrent(this.glInfo.context); - } - if (this.htmlCanvas.width !== this.newWidth) { this.htmlCanvas.width = this.newWidth ?? 0; } @@ -131,6 +125,11 @@ export class Canvas { } public setCanvasSize(width: number, height: number): void { + if (this.renderLoopRequest !== 0) { + window.cancelAnimationFrame(this.renderLoopRequest); + this.renderLoopRequest = 0; + } + this.newWidth = width; this.newHeight = height; @@ -142,11 +141,7 @@ export class Canvas { this.htmlCanvas.height = this.newHeight; } - if (this.glInfo) { - const GL = (globalThis as any).AvaloniaGL; - // make current - GL.makeContextCurrent(this.glInfo.context); - } + this.requestAnimationFrame(); } public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number): void { From cd8358303e2d9cf4edde7ca9999ad82a4c72d474 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 22 Oct 2022 14:26:03 +0100 Subject: [PATCH 34/61] 30fps resize. --- src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts index 62f62fe46f..47c501cbb7 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts @@ -217,7 +217,7 @@ export class SizeWatcher { callback(element.clientWidth, element.clientHeight); const handleResize = (args: UIEvent) => { - if (Date.now() - SizeWatcher.lastMove > 40) { + if (Date.now() - SizeWatcher.lastMove > 33) { callback(element.clientWidth, element.clientHeight); SizeWatcher.lastMove = Date.now(); } From 6f5bae821b47e4671da4456ad3acbed7b15bd6a2 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 22 Oct 2022 11:38:48 -0400 Subject: [PATCH 35/61] Fix usage of updated Screen properties This fixes the macOS build --- native/Avalonia.Native/src/OSX/Screens.mm | 4 ++-- src/Windows/Avalonia.Win32/WinScreen.cs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm index b9c75ed742..83ab1bfd01 100644 --- a/native/Avalonia.Native/src/OSX/Screens.mm +++ b/native/Avalonia.Native/src/OSX/Screens.mm @@ -41,9 +41,9 @@ public: ret->WorkingArea.X = [screen visibleFrame].origin.x; ret->WorkingArea.Y = ConvertPointY(ToAvnPoint([screen visibleFrame].origin)).Y - ret->WorkingArea.Height; - ret->PixelDensity = [screen backingScaleFactor]; + ret->Scaling = [screen backingScaleFactor]; - ret->Primary = index == 0; + ret->IsPrimary = index == 0; return S_OK; } diff --git a/src/Windows/Avalonia.Win32/WinScreen.cs b/src/Windows/Avalonia.Win32/WinScreen.cs index f103cc3b66..1038f41a17 100644 --- a/src/Windows/Avalonia.Win32/WinScreen.cs +++ b/src/Windows/Avalonia.Win32/WinScreen.cs @@ -7,7 +7,8 @@ namespace Avalonia.Win32 { private readonly IntPtr _hMonitor; - public WinScreen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary, IntPtr hMonitor) : base(pixelDensity, bounds, workingArea, primary) + public WinScreen(double scaling, PixelRect bounds, PixelRect workingArea, bool isPrimary, IntPtr hMonitor) + : base(scaling, bounds, workingArea, isPrimary) { _hMonitor = hMonitor; } From ab02289d9377cfaa38c72bcfae9cf31ce1f6478b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 23 Oct 2022 18:22:03 +0100 Subject: [PATCH 36/61] fix distortion on mobile chrome browser. --- src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts index 668212964c..385cdd4c41 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts @@ -19,7 +19,6 @@ export class AvaloniaDOM { canvas.classList.add("avalonia-canvas"); canvas.style.backgroundColor = "#ccc"; canvas.style.width = "100%"; - canvas.style.height = "100%"; canvas.style.position = "absolute"; // Native controls host From c959c0594ff111f2e1a9342cfe046a3a4d7a50c7 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 23 Oct 2022 16:08:09 -0400 Subject: [PATCH 37/61] Update palettes used in the ColorPickerPage --- .../ControlCatalog/Pages/ColorPickerPage.xaml | 21 ++++++++++++++++--- .../Pages/ColorPickerPage.xaml.cs | 1 + 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml b/samples/ControlCatalog/Pages/ColorPickerPage.xaml index 69ceaea328..c0bb95ae92 100644 --- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml @@ -24,18 +24,19 @@ HsvColor="hsv(120, 1, 1)" Margin="0,50,0,0"> - + + RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> + - + + diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs b/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs index 4671bbdb7c..52d63ded32 100644 --- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs @@ -20,6 +20,7 @@ namespace ControlCatalog.Pages Color = Colors.Blue, Margin = new Thickness(0, 50, 0, 0), HorizontalAlignment = HorizontalAlignment.Center, + Palette = new MaterialHalfColorPalette(), }; Grid.SetColumn(colorPicker, 2); Grid.SetRow(colorPicker, 1); From ca85843f99b26c7377ad81414df25567bb3f9d8e Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 23 Oct 2022 16:20:16 -0400 Subject: [PATCH 38/61] Rework ColorPicker control template duplicating from ColorView --- .../ColorPicker/ColorPicker.cs | 28 - .../Themes/Fluent/ColorPicker.xaml | 484 +++++++++++++++-- .../Themes/Simple/ColorPicker.xaml | 486 ++++++++++++++++-- 3 files changed, 907 insertions(+), 91 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs index 29f9f3c571..01cb745ba7 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs @@ -14,33 +14,5 @@ namespace Avalonia.Controls public ColorPicker() : base() { } - - /// - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - - // Until this point the ColorPicker itself is responsible to process property updates. - // This, for example, syncs Color with HsvColor and updates primitive controls. - // - // However, when the template is created, hand-off this change processing to the - // ColorView within the control template itself. Remember ColorPicker derives from - // ColorView so we don't want two instances of the same logic fighting each other. - // It is best to hand-off to the ColorView in the control template because that is the - // primary point of user-interaction for the overall control. It also simplifies binding. - // - // Keep in mind this hand-off is not possible until the template controls are created - // which is done after the ColorPicker is instantiated. The ColorPicker must still - // process updates before the template is applied to ensure all property changes in - // XAML or object initializers are handled correctly. Otherwise, there can be bugs - // such as setting the Color property doesn't work because the HsvColor is never updated - // and then the Color value is lost once the template loads (and the template ColorView - // takes over). - // - // In order to complete this hand-off, completely ignore property changes here in the - // ColorPicker. This means the ColorView in the control template is now responsible to - // process property changes and handle primary calculations. - base.ignorePropertyChanged = true; - } } } diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index 74a1df4991..1bcd17393d 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -1,8 +1,15 @@  + + + + @@ -43,39 +50,454 @@ - - + + + + + 5,5,0,0 - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml index 560d326f92..fdee64dfd2 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml @@ -1,8 +1,15 @@  + + + + @@ -42,40 +49,455 @@ - - - + + + + + + 0,0,0,0 - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From ae570453c7b33bd7abdc5cac792a9e306de2cb59 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 23 Oct 2022 16:32:52 -0400 Subject: [PATCH 39/61] Allow derived controls to call ColorSpectrum.ThirdComponent.set() --- .../ColorSpectrum/ColorSpectrum.Properties.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs index 39b7b7f660..5c7de2459b 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs @@ -240,7 +240,7 @@ namespace Avalonia.Controls.Primitives public ColorComponent ThirdComponent { get => GetValue(ThirdComponentProperty); - private set => SetValue(ThirdComponentProperty, value); + protected set => SetValue(ThirdComponentProperty, value); } } } From 8d63ed26458669180604ecf9d505b1c5bfa399bd Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 23 Oct 2022 17:49:34 -0400 Subject: [PATCH 40/61] Initial import of WinUI Expander theme --- .../Accents/FluentControlResourcesDark.xaml | 31 +++- .../Accents/FluentControlResourcesLight.xaml | 32 ++++- .../Controls/Expander.xaml | 135 +++++++++++------- 3 files changed, 143 insertions(+), 55 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index e480fb1670..c6e3259b27 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -281,7 +281,6 @@ - @@ -305,7 +304,35 @@ - + + + + + + + + + + + + + 1 + + + + + + + + + + + 0 + + + + + diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index 4f421f64a7..808436b9d5 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -278,7 +278,7 @@ - + @@ -301,7 +301,35 @@ - + + + + + + + + + + + + + 1 + + + + + + + + + + + 0 + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml index 7d5bb48333..8606ee16a3 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml @@ -1,6 +1,7 @@ + @@ -44,50 +45,65 @@ - 16 + + 48 + + + Stretch + Center + 16,0,0,0 + 20,0,8,0 + 32 + 12 + + 16 - 1 - 1,1,0,1 - 1,1,1,0 - 0,1,1,1 - 1,0,1,1 - - - - - - + 1,0,1,1 + 1,1,1,0 + + - - + + + + HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" + Foreground="{TemplateBinding Foreground}" /> + + Width="{DynamicResource ExpanderChevronButtonSize}" + Height="{DynamicResource ExpanderChevronButtonSize}" + Margin="{DynamicResource ExpanderChevronMargin}" + CornerRadius="{DynamicResource ControlCornerRadius}" + BorderBrush="{DynamicResource ExpanderChevronBorderBrush}" + BorderThickness="{DynamicResource ExpanderChevronBorderThickness}" + Background="{DynamicResource ExpanderChevronBackground}"> + @@ -98,6 +114,7 @@ + + + + + + + - - - + + + + + + + + + - - - - - + + IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" /> + IsVisible="{TemplateBinding IsExpanded, Mode=TwoWay}" + Background="{TemplateBinding Background}" + BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="{StaticResource ExpanderContentDownBorderThickness}" + MinHeight="{TemplateBinding MinHeight}" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + Padding="{TemplateBinding Padding}"> + ContentTemplate="{TemplateBinding ContentTemplate}" + HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" /> From 830587fc28e320eca8cae79d40bc71a03ddec148 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 23 Oct 2022 21:17:20 -0400 Subject: [PATCH 41/61] Remap Expander resources to Fluent v1 base resources --- .../Accents/FluentControlResourcesDark.xaml | 44 +++++++++---------- .../Accents/FluentControlResourcesLight.xaml | 42 +++++++++--------- .../Controls/Expander.xaml | 14 +++--- 3 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index c6e3259b27..60d83a3ecf 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -307,31 +307,29 @@ - - - - - - - - - - 1 - - - - - - - - - - - 0 + + + + + + + + + + + + + + + + + + + - - + + diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index 808436b9d5..632ffe6ace 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -304,31 +304,29 @@ - - - - - - - - - - 1 + + + + + + + + + - - - - - - - - - - 0 + + + + + + + + + - - + + diff --git a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml index 8606ee16a3..0a61beacae 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml @@ -52,14 +52,18 @@ Stretch Center 16,0,0,0 + 1 + 0 20,0,8,0 32 12 16 - 1,0,1,1 + 1,1,0,1 1,1,1,0 + 0,1,1,1 + 1,0,1,1 @@ -274,16 +278,16 @@ From 65a4315cd0ef70aaa520bc360dbe49624521d00c Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 23 Oct 2022 21:26:35 -0400 Subject: [PATCH 42/61] Use standard resource names --- .../Accents/FluentControlResourcesDark.xaml | 20 +++++++++---------- .../Accents/FluentControlResourcesLight.xaml | 20 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index 60d83a3ecf..d2738c231f 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -311,21 +311,21 @@ + - - - - + + + - - + + - - + + - - + + diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index 632ffe6ace..5a9293f7b2 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -308,21 +308,21 @@ + - - - - + + + - - + + - - + + - - + + From 00e570fb6b078a2ba880e42764d05d39124fa08f Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 23 Oct 2022 22:01:26 -0400 Subject: [PATCH 43/61] Initial fixes for new Expander ControlTheme --- .../Controls/Expander.xaml | 34 +++++-------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml index 0a61beacae..94aaff62db 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml @@ -56,7 +56,6 @@ 0 20,0,8,0 32 - 12 16 @@ -66,31 +65,26 @@ 1,0,1,1 - - + + + + + BorderThickness="{DynamicResource ExpanderHeaderBorderThickness}"> - - + Foreground="{TemplateBinding Foreground}" + Margin="{TemplateBinding Padding}"/> - @@ -183,16 +174,9 @@ @@ -200,7 +184,7 @@ IsVisible="{TemplateBinding IsExpanded, Mode=TwoWay}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" - BorderThickness="{StaticResource ExpanderContentDownBorderThickness}" + BorderThickness="{TemplateBinding BorderThickness}" MinHeight="{TemplateBinding MinHeight}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" From 0c84ae68b9b8e5eb22faedcbbec5361e8d9bca31 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 24 Oct 2022 01:19:50 -0400 Subject: [PATCH 44/61] Add AvaloniaXamlIlSetterTargetTypeMetadataTransformer to allow SetterTargetType --- .../AvaloniaXamlIlCompiler.cs | 2 + ...mlIlSetterTargetTypeMetadataTransformer.cs | 34 ++++++++++++++ .../AvaloniaXamlIlSetterTransformer.cs | 23 +-------- .../SetterTests.cs | 47 +++++++++++++------ 4 files changed, 70 insertions(+), 36 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index f325e6e2d6..4ece433530 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; + using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; using XamlX; using XamlX.Ast; @@ -51,6 +52,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), new AvaloniaXamlIlBindingPathParser(), new AvaloniaXamlIlPropertyPathTransformer(), + new AvaloniaXamlIlSetterTargetTypeMetadataTransformer(), new AvaloniaXamlIlSetterTransformer(), new AvaloniaXamlIlConstructorServiceProviderTransformer(), new AvaloniaXamlIlTransitionsTypeMetadataTransformer(), diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs new file mode 100644 index 0000000000..ebc6c01ba8 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs @@ -0,0 +1,34 @@ +using System.Linq; +using XamlX; +using XamlX.Ast; +using XamlX.Transform; +using XamlX.Transform.Transformers; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; + +internal class AvaloniaXamlIlSetterTargetTypeMetadataTransformer : IXamlAstTransformer +{ + public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) + { + if (node is XamlAstObjectNode on + && on.Children.FirstOrDefault(c => c is XamlAstXmlDirective + { + Namespace: XamlNamespaces.Xaml2006, + Name: "SetterTargetType" + }) is { } typeDirective) + { + var value = ((XamlAstXmlDirective)typeDirective).Values.Single(); + var type = value is XamlTypeExtensionNode typeNode ? typeNode.Value + : value is XamlAstTextNode tn ? TypeReferenceResolver.ResolveType(context, tn.Text, false, tn, true) + : null; + on.Children.Remove(typeDirective); + + if (type is null) + { + throw new XamlParseException("Unable to resolve SetterTargetType type", typeDirective); + } + return new AvaloniaXamlIlTargetTypeMetadataNode(on, type, AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style); + } + return node; + } +} 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 ef322fcce9..b9b8c9e9a2 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -9,7 +9,6 @@ using XamlX.TypeSystem; namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { - using XamlParseException = XamlX.XamlParseException; class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer { public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) @@ -28,29 +27,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (styleParent != null) { targetType = styleParent.TargetType.GetClrType() - ?? throw new XamlParseException("Can not resolve parent Style Selector type", node); + ?? throw new XamlParseException("Can not resolve parent Style Selector type. If setter is not part of the style, you can set x:SetterTargetType directive on its parent.", node); lineInfo = on; } - else - { - foreach (var p in context.ParentNodes().OfType()) - { - for (var index = 0; index < p.Children.Count; index++) - { - if (p.Children[index] is XamlAstXmlDirective d && - d.Namespace == XamlNamespaces.Xaml2006 && - d.Name == "SetterTargetType") - { - targetType = context.Configuration.TypeSystem.GetType(((XamlAstTextNode)d.Values[0]).Text); - lineInfo = d; - - break; - } - } - - if (targetType != null) break; - } - } if (targetType == null) { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs index cc1dce4de8..6fc0f2d91c 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs @@ -1,20 +1,19 @@ -using System.Linq; -using Avalonia.Data; +using Avalonia.Controls; using Avalonia.Styling; using Avalonia.UnitTests; using Xunit; -namespace Avalonia.Markup.Xaml.UnitTests +namespace Avalonia.Markup.Xaml.UnitTests; + +public class SetterTests : XamlTestBase { - public class SetterTests : XamlTestBase + [Fact] + public void SetterTargetType_Should_Understand_xType_Extensions() { - [Fact] - public void Setter_Should_Work_Outside_Of_Style_With_SetterTargetType_Attribute() + using (UnitTestApplication.Start(TestServices.StyledWindow)) { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var xaml = @" - + var xaml = @" + @@ -22,11 +21,31 @@ namespace Avalonia.Markup.Xaml.UnitTests "; - var animation = (Animation.Animation)AvaloniaRuntimeXamlLoader.Load(xaml); - var setter = (Setter)animation.Children[0].Setters[0]; + var animation = (Animation.Animation)AvaloniaRuntimeXamlLoader.Load(xaml); + var setter = (Setter)animation.Children[0].Setters[0]; + + Assert.Equal(typeof(ContentControl), setter.Property.OwnerType); + } + } + + [Fact] + public void SetterTargetType_Should_Understand_Type_From_Xmlns() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + + + +"; + var animation = (Animation.Animation)AvaloniaRuntimeXamlLoader.Load(xaml); + var setter = (Setter)animation.Children[0].Setters[0]; - Assert.IsType(setter.Value); - } + Assert.Equal(typeof(ContentControl), setter.Property.OwnerType); } } } From 7683a3bddb1a80901abd31bca3ffa1933d3839f0 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 24 Oct 2022 01:25:47 -0400 Subject: [PATCH 45/61] Restore old exception message --- .../Transformers/AvaloniaXamlIlSetterTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b9b8c9e9a2..5a6fd8246d 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -27,7 +27,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (styleParent != null) { targetType = styleParent.TargetType.GetClrType() - ?? throw new XamlParseException("Can not resolve parent Style Selector type. If setter is not part of the style, you can set x:SetterTargetType directive on its parent.", node); + ?? throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType. If setter is not part of the style, you can set x:SetterTargetType directive on its parent.", node); lineInfo = on; } From 2a547d716101c67d2077655d460bae5fbe9e5bfb Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 24 Oct 2022 14:27:53 +0200 Subject: [PATCH 46/61] Introduce GlyphMetrics --- src/Avalonia.Base/Media/FontSimulations.cs | 27 ++++++++++++++++ src/Avalonia.Base/Media/GlyphMetrics.cs | 24 ++++++++++++++ src/Avalonia.Base/Media/IGlyphTypeface.cs | 13 ++++++++ .../HeadlessPlatformStubs.cs | 13 ++++++++ src/Skia/Avalonia.Skia/FontManagerImpl.cs | 16 +++++++--- src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs | 28 +++++++++++++--- .../Media/GlyphTypefaceImpl.cs | 25 ++++++++++++++- .../HarfBuzzGlyphTypefaceImpl.cs | 32 +++++++++++++------ tests/Avalonia.UnitTests/MockGlyphTypeface.cs | 13 ++++++++ 9 files changed, 173 insertions(+), 18 deletions(-) create mode 100644 src/Avalonia.Base/Media/FontSimulations.cs create mode 100644 src/Avalonia.Base/Media/GlyphMetrics.cs diff --git a/src/Avalonia.Base/Media/FontSimulations.cs b/src/Avalonia.Base/Media/FontSimulations.cs new file mode 100644 index 0000000000..2faf53f1d8 --- /dev/null +++ b/src/Avalonia.Base/Media/FontSimulations.cs @@ -0,0 +1,27 @@ +using System; + +namespace Avalonia.Media +{ + /// + /// Specifies algorithmic style simulations to be applied to the typeface. + /// Bold and oblique simulations can be combined via bitwise OR operation. + /// + [Flags] + public enum FontSimulations : byte + { + /// + /// No simulations are performed. + /// + None = 0x0000, + + /// + /// Algorithmic emboldening is performed. + /// + Bold = 0x0001, + + /// + /// Algorithmic italicization is performed. + /// + Oblique = 0x0002 + } +} diff --git a/src/Avalonia.Base/Media/GlyphMetrics.cs b/src/Avalonia.Base/Media/GlyphMetrics.cs new file mode 100644 index 0000000000..2ee1f87d38 --- /dev/null +++ b/src/Avalonia.Base/Media/GlyphMetrics.cs @@ -0,0 +1,24 @@ +namespace Avalonia.Media; + +public readonly struct GlyphMetrics +{ + /// + /// Distance from the x-origin to the left extremum of the glyph. + /// + public int XBearing { get; init; } + + /// + /// Distance from the top extremum of the glyph to the y-origin. + /// + public int YBearing{ get; init; } + + /// + /// Distance from the left extremum of the glyph to the right extremum. + /// + public int Width{ get; init; } + + /// + /// Distance from the top extremum of the glyph to the bottom extremum. + /// + public int Height{ get; init; } +} diff --git a/src/Avalonia.Base/Media/IGlyphTypeface.cs b/src/Avalonia.Base/Media/IGlyphTypeface.cs index de2a2309ee..e65a3cb6bf 100644 --- a/src/Avalonia.Base/Media/IGlyphTypeface.cs +++ b/src/Avalonia.Base/Media/IGlyphTypeface.cs @@ -19,6 +19,19 @@ namespace Avalonia.Media /// FontMetrics Metrics { get; } + /// + /// Gets the algorithmic style simulations applied to this glyph typeface. + /// + FontSimulations FontSimulations { get; } + + /// + /// + /// + /// + /// + /// + bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics); + /// /// Returns an glyph index for the specified codepoint. /// diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index c8ac947c16..cfeb0d92d0 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -102,6 +102,8 @@ namespace Avalonia.Headless public int GlyphCount => 1337; + public FontSimulations FontSimulations => throw new NotImplementedException(); + public void Dispose() { } @@ -138,6 +140,17 @@ namespace Avalonia.Headless table = null; return false; } + + public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) + { + metrics = new GlyphMetrics + { + Height = 10, + Width = 10 + }; + + return true; + } } class HeadlessTextShaperStub : ITextShaperImpl diff --git a/src/Skia/Avalonia.Skia/FontManagerImpl.cs b/src/Skia/Avalonia.Skia/FontManagerImpl.cs index 6b5e0b3db5..90ff9652d8 100644 --- a/src/Skia/Avalonia.Skia/FontManagerImpl.cs +++ b/src/Skia/Avalonia.Skia/FontManagerImpl.cs @@ -148,11 +148,19 @@ namespace Avalonia.Skia $"Could not create glyph typeface for: {typeface.FontFamily.Name}."); } - var isFakeBold = (int)typeface.Weight >= 600 && !skTypeface.IsBold; + var fontSimulations = FontSimulations.None; - var isFakeItalic = typeface.Style == FontStyle.Italic && !skTypeface.IsItalic; - - return new GlyphTypefaceImpl(skTypeface, isFakeBold, isFakeItalic); + if((int)typeface.Weight >= 600 && !skTypeface.IsBold) + { + fontSimulations |= FontSimulations.Bold; + } + + if(typeface.Style == FontStyle.Italic && !skTypeface.IsItalic) + { + fontSimulations |= FontSimulations.Oblique; + } + + return new GlyphTypefaceImpl(skTypeface, fontSimulations); } } } diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs index 7f3faf251f..d11f4aa7d3 100644 --- a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs @@ -12,7 +12,7 @@ namespace Avalonia.Skia { private bool _isDisposed; - public GlyphTypefaceImpl(SKTypeface typeface, bool isFakeBold = false, bool isFakeItalic = false) + public GlyphTypefaceImpl(SKTypeface typeface, FontSimulations fontSimulations) { Typeface = typeface ?? throw new ArgumentNullException(nameof(typeface)); @@ -52,9 +52,7 @@ namespace Avalonia.Skia GlyphCount = Typeface.GlyphCount; - IsFakeBold = isFakeBold; - - IsFakeItalic = isFakeItalic; + FontSimulations = fontSimulations; } public Face Face { get; } @@ -63,6 +61,8 @@ namespace Avalonia.Skia public SKTypeface Typeface { get; } + public FontSimulations FontSimulations { get; } + public int ReplacementCodepoint { get; } public FontMetrics Metrics { get; } @@ -73,6 +73,26 @@ namespace Avalonia.Skia public bool IsFakeItalic { get; } + public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) + { + metrics = default; + + if (!Font.TryGetGlyphExtents(glyph, out var extents)) + { + return false; + } + + metrics = new GlyphMetrics + { + XBearing = extents.XBearing, + YBearing = extents.YBearing, + Width = extents.Width, + Height = extents.Height + }; + + return true; + } + /// public ushort GetGlyph(uint codepoint) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs index 77d0e58d3d..705c715455 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs @@ -1,10 +1,11 @@ using System; -using System.Drawing.Drawing2D; using Avalonia.Media; using Avalonia.Metadata; using HarfBuzzSharp; using SharpDX.DirectWrite; using FontMetrics = Avalonia.Media.FontMetrics; +using FontSimulations = Avalonia.Media.FontSimulations; +using GlyphMetrics = Avalonia.Media.GlyphMetrics; namespace Avalonia.Direct2D1.Media { @@ -82,6 +83,8 @@ namespace Avalonia.Direct2D1.Media public int GlyphCount { get; set; } + public FontSimulations FontSimulations => FontSimulations.None; + /// public ushort GetGlyph(uint codepoint) { @@ -135,6 +138,26 @@ namespace Avalonia.Direct2D1.Media return Font.GetHorizontalGlyphAdvances(glyphIndices); } + public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) + { + metrics = default; + + if (!Font.TryGetGlyphExtents(glyph, out var extents)) + { + return false; + } + + metrics = new GlyphMetrics + { + XBearing = extents.XBearing, + YBearing = extents.YBearing, + Width = extents.Width, + Height = extents.Height + }; + + return true; + } + private void Dispose(bool disposing) { if (_isDisposed) diff --git a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs index 3bcbc2efbd..5b11345f16 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs @@ -10,7 +10,7 @@ namespace Avalonia.UnitTests private bool _isDisposed; private Blob _blob; - public HarfBuzzGlyphTypefaceImpl(Stream data, bool isFakeBold = false, bool isFakeItalic = false) + public HarfBuzzGlyphTypefaceImpl(Stream data) { _blob = Blob.FromStream(data); @@ -45,10 +45,6 @@ namespace Avalonia.UnitTests }; GlyphCount = Face.GlyphCount; - - IsFakeBold = isFakeBold; - - IsFakeItalic = isFakeItalic; } public FontMetrics Metrics { get; } @@ -58,10 +54,8 @@ namespace Avalonia.UnitTests public Font Font { get; } public int GlyphCount { get; set; } - - public bool IsFakeBold { get; } - - public bool IsFakeItalic { get; } + + public FontSimulations FontSimulations { get; } /// public ushort GetGlyph(uint codepoint) @@ -162,5 +156,25 @@ namespace Avalonia.UnitTests Dispose(true); GC.SuppressFinalize(this); } + + public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) + { + metrics = default; + + if (!Font.TryGetGlyphExtents(glyph, out var extents)) + { + return false; + } + + metrics = new GlyphMetrics + { + XBearing = extents.XBearing, + YBearing = extents.YBearing, + Width = extents.Width, + Height = extents.Height + }; + + return true; + } } } diff --git a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs index a1c492a7f1..bd9d8e5adf 100644 --- a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs +++ b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs @@ -15,6 +15,8 @@ namespace Avalonia.UnitTests public int GlyphCount => 1337; + public FontSimulations FontSimulations => throw new NotImplementedException(); + public ushort GetGlyph(uint codepoint) { return (ushort)codepoint; @@ -56,5 +58,16 @@ namespace Avalonia.UnitTests table = null; return false; } + + public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) + { + metrics = new GlyphMetrics + { + Width = 10, + Height = 10 + }; + + return true; + } } } From 822d57cac6369d72aaee4d0a4a5ca0b5259cc8d7 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 24 Oct 2022 14:45:25 +0200 Subject: [PATCH 47/61] Use LangVersion preview for all projects --- build/SharedVersion.props | 2 +- .../Avalonia.Markup.Xaml.Loader.csproj | 1 - src/Markup/Avalonia.Markup/Avalonia.Markup.csproj | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/build/SharedVersion.props b/build/SharedVersion.props index 1b60bb4df9..5838519596 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -8,7 +8,7 @@ https://github.com/AvaloniaUI/Avalonia/ true CS1591 - latest + preview MIT Icon.png Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly. diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj index f9be3fd62a..1dc7ce5e99 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj @@ -5,7 +5,6 @@ true Avalonia.Markup.Xaml.Loader $(DefineConstants);XAMLX_INTERNAL - 11 diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index 6b25cbbeab..6711c3dd3d 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -2,7 +2,6 @@ net6.0;netstandard2.0 Avalonia - 11 From fd46119d74fd7ef3779311bf0b91e26570288e54 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 24 Oct 2022 16:56:17 +0200 Subject: [PATCH 48/61] Add failing test for Viewbox DataContext binding. --- .../ViewboxTests.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs index 4ffd314857..629408bcba 100644 --- a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs @@ -1,4 +1,5 @@ using Avalonia.Controls.Shapes; +using Avalonia.Data; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.UnitTests; @@ -207,6 +208,26 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Size(200, 200), target.DesiredSize); } + [Fact] + public void Child_DataContext_Binding_Works() + { + var data = new + { + Foo = "foo", + }; + + var target = new Viewbox() + { + DataContext = data, + Child = new Canvas + { + [!Canvas.DataContextProperty] = new Binding("Foo"), + }, + }; + + Assert.Equal("foo", target.Child.DataContext); + } + private bool TryGetScale(Viewbox viewbox, out Vector scale) { if (viewbox.InternalTransform is null) From 4740955ef4cdeac20367c2b3023967bac4e0585d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 24 Oct 2022 16:58:30 +0200 Subject: [PATCH 49/61] Set Viewbox logical parent. Fixes `DataContext` binding in `Viewbox.Child`. --- src/Avalonia.Controls/Viewbox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs index aabfd3ef18..07d877142e 100644 --- a/src/Avalonia.Controls/Viewbox.cs +++ b/src/Avalonia.Controls/Viewbox.cs @@ -42,6 +42,7 @@ namespace Avalonia.Controls // can be applied independently of the Viewbox and Child transforms. _containerVisual = new ViewboxContainer(); _containerVisual.RenderTransformOrigin = RelativePoint.TopLeft; + ((ISetLogicalParent)_containerVisual).SetParent(this); VisualChildren.Add(_containerVisual); } From 04b8f3b38833b6ee6c0ade8ead624b328b137d65 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 25 Oct 2022 10:58:18 +0200 Subject: [PATCH 50/61] Fix headless --- src/Avalonia.Headless/HeadlessPlatformStubs.cs | 2 +- tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index cfeb0d92d0..76948e9286 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -102,7 +102,7 @@ namespace Avalonia.Headless public int GlyphCount => 1337; - public FontSimulations FontSimulations => throw new NotImplementedException(); + public FontSimulations FontSimulations { get; } public void Dispose() { diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index 7998c95877..a748f6cf00 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs @@ -103,7 +103,7 @@ namespace Avalonia.Skia.UnitTests.Media } } - return new GlyphTypefaceImpl(skTypeface); + return new GlyphTypefaceImpl(skTypeface, FontSimulations.None); } } } From f2fda7bcdb4253efd5df7ff86ab39b23d7ba77ae Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 25 Oct 2022 11:38:37 +0200 Subject: [PATCH 51/61] Fix xml comment --- src/Avalonia.Base/Media/IGlyphTypeface.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Media/IGlyphTypeface.cs b/src/Avalonia.Base/Media/IGlyphTypeface.cs index e65a3cb6bf..9e1e52cb73 100644 --- a/src/Avalonia.Base/Media/IGlyphTypeface.cs +++ b/src/Avalonia.Base/Media/IGlyphTypeface.cs @@ -25,11 +25,13 @@ namespace Avalonia.Media FontSimulations FontSimulations { get; } /// - /// + /// Tries to get a glyph's metrics in em units. /// - /// - /// - /// + /// The glyph id. + /// The glyph metrics. + /// + /// true if an glyph's metrics was found, false otherwise. + /// bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics); /// From 4c41c9c7dd2b6cbc2f7aa97df14a42e4ce288459 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 25 Oct 2022 12:11:10 +0100 Subject: [PATCH 52/61] Wasm optimal defaults (#9267) * introduce optimal defaults for wasm builds * dont include satelite assemblies. * always preventDefault mouse and pointer * fix argument order. * set initial heap size. * put properties in targets. * fix condition * restore props file * remove duplicate code. * allow people to override initial heap size... but not some critical properties, * add special ld flag. --- src/Web/Avalonia.Web/Avalonia.Web.props | 4 +-- src/Web/Avalonia.Web/Avalonia.Web.targets | 30 +++++++++++++++++++ src/Web/Avalonia.Web/Interop/CanvasHelper.cs | 2 +- .../webapp/modules/avalonia/input.ts | 20 +++++-------- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/Web/Avalonia.Web/Avalonia.Web.props b/src/Web/Avalonia.Web/Avalonia.Web.props index 6c975cd284..668dd20789 100644 --- a/src/Web/Avalonia.Web/Avalonia.Web.props +++ b/src/Web/Avalonia.Web/Avalonia.Web.props @@ -1,5 +1,5 @@ - + - $(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\interop.js" + 16384000 diff --git a/src/Web/Avalonia.Web/Avalonia.Web.targets b/src/Web/Avalonia.Web/Avalonia.Web.targets index d1bec2aa93..b6a09b33ef 100644 --- a/src/Web/Avalonia.Web/Avalonia.Web.targets +++ b/src/Web/Avalonia.Web/Avalonia.Web.targets @@ -4,4 +4,34 @@ + + + True + $(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\interop.js" + $(EmccExtraLDFlags) -sERROR_ON_UNDEFINED_SYMBOLS=0 + true + + + + true + full + true + -Oz + -Oz + false + false + 0 + false + true + false + false + false + false + false + false + true + true + en + false + diff --git a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs index efa94916fa..5bbe503bc1 100644 --- a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs +++ b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs @@ -33,7 +33,7 @@ internal static partial class CanvasHelper public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop); [JSImport("Canvas.setCanvasSize", AvaloniaModule.MainModuleName)] - public static partial void SetCanvasSize(JSObject canvas, int height, int width); + public static partial void SetCanvasSize(JSObject canvas, int width, int height); [JSImport("Canvas.initGL", AvaloniaModule.MainModuleName)] private static partial JSObject InitGL( diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts index ddc1f54ae7..faede82e0d 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts @@ -98,27 +98,23 @@ export class InputHelper { wheelCallback: (args: WheelEvent) => boolean ) { const pointerMoveHandler = (args: PointerEvent) => { - if (pointerMoveCallback(args)) { - args.preventDefault(); - } + pointerMoveCallback(args); + args.preventDefault(); }; const pointerDownHandler = (args: PointerEvent) => { - if (pointerDownCallback(args)) { - args.preventDefault(); - } + pointerDownCallback(args); + args.preventDefault(); }; const pointerUpHandler = (args: PointerEvent) => { - if (pointerUpCallback(args)) { - args.preventDefault(); - } + pointerUpCallback(args); + args.preventDefault(); }; const wheelHandler = (args: WheelEvent) => { - if (wheelCallback(args)) { - args.preventDefault(); - } + wheelCallback(args); + args.preventDefault(); }; element.addEventListener("pointermove", pointerMoveHandler); From c8fae36d36047869bac3a1c055ec30a3d0bd9757 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 25 Oct 2022 18:49:09 +0100 Subject: [PATCH 53/61] implement touch. --- src/Web/Avalonia.Web/AvaloniaView.cs | 113 +++++++++++++----- src/Web/Avalonia.Web/BrowserTopLevelImpl.cs | 8 ++ src/Web/Avalonia.Web/Interop/InputHelper.cs | 12 ++ .../webapp/modules/avalonia/input.ts | 60 ++++++++++ 4 files changed, 166 insertions(+), 27 deletions(-) diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index 098b06a0a2..d61733b722 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -97,6 +97,8 @@ namespace Avalonia.Web OnCompositionEnd); InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, OnWheel); + + InputHelper.SubscribeTouchEvents(_containerElement, OnTouchStart, OnTouchEnd, OnTouchCancel, OnTouchMove); var skiaOptions = AvaloniaLocator.Current.GetService(); @@ -139,6 +141,62 @@ namespace Avalonia.Web InputHelper.FocusElement(_containerElement); } + private void OnTouchStart(JSObject arg, JSObject touch) + { + var x = touch.GetPropertyAsDouble("clientX"); + var y = touch.GetPropertyAsDouble("clientY"); + long identifier = touch.GetPropertyAsInt32("identifier"); + + _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchBegin, new Point(x, y), + GetTouchModifiers(arg), identifier); + } + + private void OnTouchEnd(JSObject arg, JSObject touch) + { + var x = touch.GetPropertyAsDouble("clientX"); + var y = touch.GetPropertyAsDouble("clientY"); + long identifier = touch.GetPropertyAsInt32("identifier"); + + _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchEnd, new Point(x, y), + GetTouchModifiers(arg), identifier); + } + + private void OnTouchCancel(JSObject arg, JSObject touch) + { + var x = touch.GetPropertyAsDouble("clientX"); + var y = touch.GetPropertyAsDouble("clientY"); + long identifier = touch.GetPropertyAsInt32("identifier"); + + _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchCancel, new Point(x, y), + GetTouchModifiers(arg), identifier); + } + + private void OnTouchMove(JSObject arg, JSObject touch) + { + var x = touch.GetPropertyAsDouble("clientX"); + var y = touch.GetPropertyAsDouble("clientY"); + long identifier = touch.GetPropertyAsInt32("identifier"); + + _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchUpdate, new Point(x, y), + GetTouchModifiers(arg), identifier); + } + + private static RawInputModifiers GetTouchModifiers(JSObject e) + { + var modifiers = RawInputModifiers.None; + + if (e.GetPropertyAsBoolean("ctrlKey")) + modifiers |= RawInputModifiers.Control; + if (e.GetPropertyAsBoolean("altKey")) + modifiers |= RawInputModifiers.Alt; + if(e.GetPropertyAsBoolean("shiftKey")) + modifiers |= RawInputModifiers.Shift; + if(e.GetPropertyAsBoolean("metaKey")) + modifiers |= RawInputModifiers.Meta; + + return modifiers; + } + private static RawPointerPoint ExtractRawPointerFromJSArgs(JSObject args) { var point = new RawPointerPoint @@ -155,30 +213,32 @@ namespace Avalonia.Web private bool OnPointerMove(JSObject args) { - var type = args.GetPropertyAsString("pointertype"); + var pointerType = args.GetPropertyAsString("pointerType"); + + if (pointerType == "touch") + return false; var point = ExtractRawPointerFromJSArgs(args); - - return _topLevelImpl.RawPointerEvent(RawPointerEventType.Move, type!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); + + return _topLevelImpl.RawPointerEvent(RawPointerEventType.Move, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); } private bool OnPointerDown(JSObject args) { var pointerType = args.GetPropertyAsString("pointerType"); - var type = pointerType switch + if (pointerType == "touch") + return false; + + var type = args.GetPropertyAsInt32("button") switch { - "touch" => RawPointerEventType.TouchBegin, - _ => args.GetPropertyAsInt32("button") switch - { - 0 => RawPointerEventType.LeftButtonDown, - 1 => RawPointerEventType.MiddleButtonDown, - 2 => RawPointerEventType.RightButtonDown, - 3 => RawPointerEventType.XButton1Down, - 4 => RawPointerEventType.XButton2Down, - // 5 => Pen eraser button, - _ => RawPointerEventType.Move - } + 0 => RawPointerEventType.LeftButtonDown, + 1 => RawPointerEventType.MiddleButtonDown, + 2 => RawPointerEventType.RightButtonDown, + 3 => RawPointerEventType.XButton1Down, + 4 => RawPointerEventType.XButton2Down, + // 5 => Pen eraser button, + _ => RawPointerEventType.Move }; var point = ExtractRawPointerFromJSArgs(args); @@ -190,19 +250,18 @@ namespace Avalonia.Web { var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse"; - var type = pointerType switch + if (pointerType == "touch") + return false; + + var type = args.GetPropertyAsInt32("button") switch { - "touch" => RawPointerEventType.TouchEnd, - _ => args.GetPropertyAsInt32("button") switch - { - 0 => RawPointerEventType.LeftButtonUp, - 1 => RawPointerEventType.MiddleButtonUp, - 2 => RawPointerEventType.RightButtonUp, - 3 => RawPointerEventType.XButton1Up, - 4 => RawPointerEventType.XButton2Up, - // 5 => Pen eraser button, - _ => RawPointerEventType.Move - } + 0 => RawPointerEventType.LeftButtonUp, + 1 => RawPointerEventType.MiddleButtonUp, + 2 => RawPointerEventType.RightButtonUp, + 3 => RawPointerEventType.XButton1Up, + 4 => RawPointerEventType.XButton2Up, + // 5 => Pen eraser button, + _ => RawPointerEventType.Move }; var point = ExtractRawPointerFromJSArgs(args); diff --git a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs index b955da6df2..bb1d79bd3d 100644 --- a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs +++ b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs @@ -64,6 +64,14 @@ namespace Avalonia.Web Resized?.Invoke(newSize, PlatformResizeReason.User); } } + + public void RawTouchEvent(RawPointerEventType type, Point p, RawInputModifiers modifiers, long touchPointId) + { + if (_inputRoot is { } && Input is { } input) + { + input.Invoke(new RawTouchEventArgs(_touchDevice, Timestamp, _inputRoot, type, p, modifiers, touchPointId)); + } + } public bool RawPointerEvent( RawPointerEventType eventType, string pointerType, diff --git a/src/Web/Avalonia.Web/Interop/InputHelper.cs b/src/Web/Avalonia.Web/Interop/InputHelper.cs index cfec9f30dc..3329026b70 100644 --- a/src/Web/Avalonia.Web/Interop/InputHelper.cs +++ b/src/Web/Avalonia.Web/Interop/InputHelper.cs @@ -37,6 +37,18 @@ internal static partial class InputHelper Func pointerUp, [JSMarshalAs>] Func wheel); + + [JSImport("InputHelper.subscribeTouchEvents", AvaloniaModule.MainModuleName)] + public static partial void SubscribeTouchEvents( + JSObject htmlElement, + [JSMarshalAs>] + Action touchStart, + [JSMarshalAs>] + Action touchEnd, + [JSMarshalAs>] + Action touchCancel, + [JSMarshalAs>] + Action touchMove); [JSImport("InputHelper.subscribeInputEvents", AvaloniaModule.MainModuleName)] diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts index faede82e0d..74e87a8e2e 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts @@ -130,6 +130,66 @@ export class InputHelper { }; } + public static subscribeTouchEvents( + element: HTMLInputElement, + touchStartCallback: (args: TouchEvent, touch: Touch) => void, + touchEndCallback: (args: TouchEvent, touch: Touch) => void, + touchCancelCallback: (args: TouchEvent, touch: Touch) => void, + touchMoveCallback: (args: TouchEvent, touch: Touch) => void + ) { + const touchStartHandler = (args: TouchEvent) => { + for (let i = 0; i < args.changedTouches.length; i++) { + const touch = args.changedTouches.item(i); + if (touch) { + touchStartCallback(args, touch); + } + } + args.preventDefault(); + }; + + const touchEndHandler = (args: TouchEvent) => { + for (let i = 0; i < args.changedTouches.length; i++) { + const touch = args.changedTouches.item(i); + if (touch) { + touchEndCallback(args, touch); + } + } + args.preventDefault(); + }; + + const touchCancelHandler = (args: TouchEvent) => { + for (let i = 0; i < args.changedTouches.length; i++) { + const touch = args.changedTouches.item(i); + if (touch) { + touchCancelCallback(args, touch); + } + } + args.preventDefault(); + }; + + const touchMoveHandler = (args: TouchEvent) => { + for (let i = 0; i < args.changedTouches.length; i++) { + const touch = args.changedTouches.item(i); + if (touch) { + touchMoveCallback(args, touch); + } + } + args.preventDefault(); + }; + + element.addEventListener("touchstart", touchStartHandler); + element.addEventListener("touchend", touchEndHandler); + element.addEventListener("touchcancel", touchCancelHandler); + element.addEventListener("touchmove", touchMoveHandler); + + return () => { + element.removeEventListener("touchstart", touchStartHandler); + element.removeEventListener("touchend", touchEndHandler); + element.removeEventListener("touchcancel", touchCancelHandler); + element.removeEventListener("touchmove", touchMoveHandler); + }; + } + public static subscribeInputEvents( element: HTMLInputElement, inputCallback: (value: string) => boolean From 985e4cf2ab983d25575d210edc1555ba7776e3c9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 25 Oct 2022 21:50:45 +0100 Subject: [PATCH 54/61] implement touch via pointer events only. --- src/Web/Avalonia.Web/AvaloniaView.cs | 108 +++++++++--------- src/Web/Avalonia.Web/BrowserTopLevelImpl.cs | 11 +- src/Web/Avalonia.Web/Interop/InputHelper.cs | 14 +-- .../webapp/modules/avalonia/input.ts | 68 ++--------- 4 files changed, 74 insertions(+), 127 deletions(-) diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index d61733b722..8d8d6691ec 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -52,13 +52,13 @@ namespace Avalonia.Web } _containerElement = hostContent.GetPropertyAsJSObject("host") - ?? throw new InvalidOperationException("Host cannot be null"); + ?? throw new InvalidOperationException("Host cannot be null"); _canvas = hostContent.GetPropertyAsJSObject("canvas") - ?? throw new InvalidOperationException("Canvas cannot be null"); + ?? throw new InvalidOperationException("Canvas cannot be null"); _nativeControlsContainer = hostContent.GetPropertyAsJSObject("nativeHost") - ?? throw new InvalidOperationException("NativeHost cannot be null"); + ?? throw new InvalidOperationException("NativeHost cannot be null"); _inputElement = hostContent.GetPropertyAsJSObject("inputElement") - ?? throw new InvalidOperationException("InputElement cannot be null"); + ?? throw new InvalidOperationException("InputElement cannot be null"); _splash = DomHelper.GetElementById("avalonia-splash"); @@ -96,9 +96,8 @@ namespace Avalonia.Web OnCompositionUpdate, OnCompositionEnd); - InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, OnWheel); - - InputHelper.SubscribeTouchEvents(_containerElement, OnTouchStart, OnTouchEnd, OnTouchCancel, OnTouchMove); + InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, + OnPointerCancel, OnWheel); var skiaOptions = AvaloniaLocator.Current.GetService(); @@ -119,7 +118,12 @@ namespace Avalonia.Web _context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024); } - _topLevelImpl.Surfaces = new[] { new BrowserSkiaSurface(_context, _jsGlInfo, ColorType, new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, GRSurfaceOrigin.BottomLeft) }; + _topLevelImpl.Surfaces = new[] + { + new BrowserSkiaSurface(_context, _jsGlInfo, ColorType, + new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, + GRSurfaceOrigin.BottomLeft) + }; } else { @@ -137,48 +141,8 @@ namespace Avalonia.Web DomHelper.ObserveSize(host, null, OnSizeChanged); CanvasHelper.RequestAnimationFrame(_canvas, true); - - InputHelper.FocusElement(_containerElement); - } - private void OnTouchStart(JSObject arg, JSObject touch) - { - var x = touch.GetPropertyAsDouble("clientX"); - var y = touch.GetPropertyAsDouble("clientY"); - long identifier = touch.GetPropertyAsInt32("identifier"); - - _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchBegin, new Point(x, y), - GetTouchModifiers(arg), identifier); - } - - private void OnTouchEnd(JSObject arg, JSObject touch) - { - var x = touch.GetPropertyAsDouble("clientX"); - var y = touch.GetPropertyAsDouble("clientY"); - long identifier = touch.GetPropertyAsInt32("identifier"); - - _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchEnd, new Point(x, y), - GetTouchModifiers(arg), identifier); - } - - private void OnTouchCancel(JSObject arg, JSObject touch) - { - var x = touch.GetPropertyAsDouble("clientX"); - var y = touch.GetPropertyAsDouble("clientY"); - long identifier = touch.GetPropertyAsInt32("identifier"); - - _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchCancel, new Point(x, y), - GetTouchModifiers(arg), identifier); - } - - private void OnTouchMove(JSObject arg, JSObject touch) - { - var x = touch.GetPropertyAsDouble("clientX"); - var y = touch.GetPropertyAsDouble("clientY"); - long identifier = touch.GetPropertyAsInt32("identifier"); - - _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchUpdate, new Point(x, y), - GetTouchModifiers(arg), identifier); + InputHelper.FocusElement(_containerElement); } private static RawInputModifiers GetTouchModifiers(JSObject e) @@ -214,9 +178,16 @@ namespace Avalonia.Web private bool OnPointerMove(JSObject args) { var pointerType = args.GetPropertyAsString("pointerType"); - + if (pointerType == "touch") - return false; + { + var x = args.GetPropertyAsDouble("clientX"); + var y = args.GetPropertyAsDouble("clientY"); + long identifier = args.GetPropertyAsInt32("identifier"); + + return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchUpdate, new Point(x, y), + GetTouchModifiers(args), identifier); + } var point = ExtractRawPointerFromJSArgs(args); @@ -228,7 +199,14 @@ namespace Avalonia.Web var pointerType = args.GetPropertyAsString("pointerType"); if (pointerType == "touch") - return false; + { + var x = args.GetPropertyAsDouble("clientX"); + var y = args.GetPropertyAsDouble("clientY"); + long identifier = args.GetPropertyAsInt32("identifier"); + + return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchBegin, new Point(x, y), + GetTouchModifiers(args), identifier); + } var type = args.GetPropertyAsInt32("button") switch { @@ -251,7 +229,14 @@ namespace Avalonia.Web var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse"; if (pointerType == "touch") - return false; + { + var x = args.GetPropertyAsDouble("clientX"); + var y = args.GetPropertyAsDouble("clientY"); + long identifier = args.GetPropertyAsInt32("identifier"); + + return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchEnd, new Point(x, y), + GetTouchModifiers(args), identifier); + } var type = args.GetPropertyAsInt32("button") switch { @@ -268,6 +253,23 @@ namespace Avalonia.Web return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); } + + private bool OnPointerCancel(JSObject args) + { + var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse"; + + if (pointerType == "touch") + { + var x = args.GetPropertyAsDouble("clientX"); + var y = args.GetPropertyAsDouble("clientY"); + long identifier = args.GetPropertyAsInt32("identifier"); + + return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchCancel, new Point(x, y), + GetTouchModifiers(args), identifier); + } + + return false; + } private bool OnWheel(JSObject args) { diff --git a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs index bb1d79bd3d..3c42deffd8 100644 --- a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs +++ b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs @@ -65,12 +65,19 @@ namespace Avalonia.Web } } - public void RawTouchEvent(RawPointerEventType type, Point p, RawInputModifiers modifiers, long touchPointId) + public bool RawTouchEvent(RawPointerEventType type, Point p, RawInputModifiers modifiers, long touchPointId) { if (_inputRoot is { } && Input is { } input) { - input.Invoke(new RawTouchEventArgs(_touchDevice, Timestamp, _inputRoot, type, p, modifiers, touchPointId)); + var args = new RawTouchEventArgs(_touchDevice, Timestamp, _inputRoot, type, p, modifiers, + touchPointId); + + input.Invoke(args); + + return args.Handled; } + + return false; } public bool RawPointerEvent( diff --git a/src/Web/Avalonia.Web/Interop/InputHelper.cs b/src/Web/Avalonia.Web/Interop/InputHelper.cs index 3329026b70..0100701714 100644 --- a/src/Web/Avalonia.Web/Interop/InputHelper.cs +++ b/src/Web/Avalonia.Web/Interop/InputHelper.cs @@ -36,19 +36,9 @@ internal static partial class InputHelper [JSMarshalAs>] Func pointerUp, [JSMarshalAs>] + Func pointerCancel, + [JSMarshalAs>] Func wheel); - - [JSImport("InputHelper.subscribeTouchEvents", AvaloniaModule.MainModuleName)] - public static partial void SubscribeTouchEvents( - JSObject htmlElement, - [JSMarshalAs>] - Action touchStart, - [JSMarshalAs>] - Action touchEnd, - [JSMarshalAs>] - Action touchCancel, - [JSMarshalAs>] - Action touchMove); [JSImport("InputHelper.subscribeInputEvents", AvaloniaModule.MainModuleName)] diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts index 74e87a8e2e..f5a6c6e806 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts @@ -95,6 +95,7 @@ export class InputHelper { pointerMoveCallback: (args: PointerEvent) => boolean, pointerDownCallback: (args: PointerEvent) => boolean, pointerUpCallback: (args: PointerEvent) => boolean, + pointerCancelCallback: (args: PointerEvent) => boolean, wheelCallback: (args: WheelEvent) => boolean ) { const pointerMoveHandler = (args: PointerEvent) => { @@ -112,6 +113,11 @@ export class InputHelper { args.preventDefault(); }; + const pointerCancelHandler = (args: PointerEvent) => { + pointerCancelCallback(args); + args.preventDefault(); + }; + const wheelHandler = (args: WheelEvent) => { wheelCallback(args); args.preventDefault(); @@ -121,75 +127,17 @@ export class InputHelper { element.addEventListener("pointerdown", pointerDownHandler); element.addEventListener("pointerup", pointerUpHandler); element.addEventListener("wheel", wheelHandler); + element.addEventListener("pointercancel", pointerCancelHandler); return () => { element.removeEventListener("pointerover", pointerMoveHandler); element.removeEventListener("pointerdown", pointerDownHandler); element.removeEventListener("pointerup", pointerUpHandler); + element.removeEventListener("pointercancel", pointerCancelHandler); element.removeEventListener("wheel", wheelHandler); }; } - public static subscribeTouchEvents( - element: HTMLInputElement, - touchStartCallback: (args: TouchEvent, touch: Touch) => void, - touchEndCallback: (args: TouchEvent, touch: Touch) => void, - touchCancelCallback: (args: TouchEvent, touch: Touch) => void, - touchMoveCallback: (args: TouchEvent, touch: Touch) => void - ) { - const touchStartHandler = (args: TouchEvent) => { - for (let i = 0; i < args.changedTouches.length; i++) { - const touch = args.changedTouches.item(i); - if (touch) { - touchStartCallback(args, touch); - } - } - args.preventDefault(); - }; - - const touchEndHandler = (args: TouchEvent) => { - for (let i = 0; i < args.changedTouches.length; i++) { - const touch = args.changedTouches.item(i); - if (touch) { - touchEndCallback(args, touch); - } - } - args.preventDefault(); - }; - - const touchCancelHandler = (args: TouchEvent) => { - for (let i = 0; i < args.changedTouches.length; i++) { - const touch = args.changedTouches.item(i); - if (touch) { - touchCancelCallback(args, touch); - } - } - args.preventDefault(); - }; - - const touchMoveHandler = (args: TouchEvent) => { - for (let i = 0; i < args.changedTouches.length; i++) { - const touch = args.changedTouches.item(i); - if (touch) { - touchMoveCallback(args, touch); - } - } - args.preventDefault(); - }; - - element.addEventListener("touchstart", touchStartHandler); - element.addEventListener("touchend", touchEndHandler); - element.addEventListener("touchcancel", touchCancelHandler); - element.addEventListener("touchmove", touchMoveHandler); - - return () => { - element.removeEventListener("touchstart", touchStartHandler); - element.removeEventListener("touchend", touchEndHandler); - element.removeEventListener("touchcancel", touchCancelHandler); - element.removeEventListener("touchmove", touchMoveHandler); - }; - } - public static subscribeInputEvents( element: HTMLInputElement, inputCallback: (value: string) => boolean From 385c26741a1aec9b16d9c354ec6182fd0971a0fa Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 25 Oct 2022 21:59:04 +0100 Subject: [PATCH 55/61] remove duplicated code. --- src/Web/Avalonia.Web/AvaloniaView.cs | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index 8d8d6691ec..bd08940306 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -144,22 +144,6 @@ namespace Avalonia.Web InputHelper.FocusElement(_containerElement); } - - private static RawInputModifiers GetTouchModifiers(JSObject e) - { - var modifiers = RawInputModifiers.None; - - if (e.GetPropertyAsBoolean("ctrlKey")) - modifiers |= RawInputModifiers.Control; - if (e.GetPropertyAsBoolean("altKey")) - modifiers |= RawInputModifiers.Alt; - if(e.GetPropertyAsBoolean("shiftKey")) - modifiers |= RawInputModifiers.Shift; - if(e.GetPropertyAsBoolean("metaKey")) - modifiers |= RawInputModifiers.Meta; - - return modifiers; - } private static RawPointerPoint ExtractRawPointerFromJSArgs(JSObject args) { @@ -186,7 +170,7 @@ namespace Avalonia.Web long identifier = args.GetPropertyAsInt32("identifier"); return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchUpdate, new Point(x, y), - GetTouchModifiers(args), identifier); + GetModifiers(args), identifier); } var point = ExtractRawPointerFromJSArgs(args); @@ -205,7 +189,7 @@ namespace Avalonia.Web long identifier = args.GetPropertyAsInt32("identifier"); return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchBegin, new Point(x, y), - GetTouchModifiers(args), identifier); + GetModifiers(args), identifier); } var type = args.GetPropertyAsInt32("button") switch @@ -235,7 +219,7 @@ namespace Avalonia.Web long identifier = args.GetPropertyAsInt32("identifier"); return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchEnd, new Point(x, y), - GetTouchModifiers(args), identifier); + GetModifiers(args), identifier); } var type = args.GetPropertyAsInt32("button") switch @@ -265,7 +249,7 @@ namespace Avalonia.Web long identifier = args.GetPropertyAsInt32("identifier"); return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchCancel, new Point(x, y), - GetTouchModifiers(args), identifier); + GetModifiers(args), identifier); } return false; From 5f5d596b1ccad47fab45a424117f53734e29dafe Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 25 Oct 2022 18:18:25 -0400 Subject: [PATCH 56/61] Fix multitouch and add historical points support --- src/Web/Avalonia.Web/Avalonia.Web.csproj | 1 + src/Web/Avalonia.Web/AvaloniaView.cs | 113 +++++++++--------- src/Web/Avalonia.Web/BrowserTopLevelImpl.cs | 26 ++-- src/Web/Avalonia.Web/Interop/InputHelper.cs | 4 + .../webapp/modules/avalonia/input.ts | 4 + 5 files changed, 71 insertions(+), 77 deletions(-) diff --git a/src/Web/Avalonia.Web/Avalonia.Web.csproj b/src/Web/Avalonia.Web/Avalonia.Web.csproj index cdfa095865..88b23cdad2 100644 --- a/src/Web/Avalonia.Web/Avalonia.Web.csproj +++ b/src/Web/Avalonia.Web/Avalonia.Web.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index bd08940306..37614399ee 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -1,5 +1,9 @@ using System; +using System.Collections.Generic; +using System.Reflection; using System.Runtime.InteropServices.JavaScript; + +using Avalonia.Collections.Pooled; using Avalonia.Controls; using Avalonia.Controls.Embedding; using Avalonia.Controls.Platform; @@ -18,6 +22,7 @@ namespace Avalonia.Web [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings public partial class AvaloniaView : ITextInputMethodImpl { + private static readonly PooledList s_intermediatePointsPooledList = new(ClearMode.Never); private readonly BrowserTopLevelImpl _topLevelImpl; private EmbeddableControlRoot _topLevel; @@ -162,94 +167,84 @@ namespace Avalonia.Web private bool OnPointerMove(JSObject args) { var pointerType = args.GetPropertyAsString("pointerType"); + var point = ExtractRawPointerFromJSArgs(args); + var type = pointerType switch + { + "touch" => RawPointerEventType.TouchUpdate, + _ => RawPointerEventType.Move + }; - if (pointerType == "touch") + var coalescedEvents = new Lazy?>(() => { - var x = args.GetPropertyAsDouble("clientX"); - var y = args.GetPropertyAsDouble("clientY"); - long identifier = args.GetPropertyAsInt32("identifier"); + var points = InputHelper.GetCoalescedEvents(args); + s_intermediatePointsPooledList.Clear(); + s_intermediatePointsPooledList.Capacity = points.Length - 1; - return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchUpdate, new Point(x, y), - GetModifiers(args), identifier); - } + // Skip the last one, as it is already processed point. + for (var i = 0; i < points.Length - 1; i++) + { + var point = points[i]; + s_intermediatePointsPooledList.Add(ExtractRawPointerFromJSArgs(point)); + } - var point = ExtractRawPointerFromJSArgs(args); - - return _topLevelImpl.RawPointerEvent(RawPointerEventType.Move, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); + return s_intermediatePointsPooledList; + }); + + return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"), coalescedEvents); } private bool OnPointerDown(JSObject args) { - var pointerType = args.GetPropertyAsString("pointerType"); - - if (pointerType == "touch") - { - var x = args.GetPropertyAsDouble("clientX"); - var y = args.GetPropertyAsDouble("clientY"); - long identifier = args.GetPropertyAsInt32("identifier"); - - return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchBegin, new Point(x, y), - GetModifiers(args), identifier); - } - - var type = args.GetPropertyAsInt32("button") switch + var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse"; + var type = pointerType switch { - 0 => RawPointerEventType.LeftButtonDown, - 1 => RawPointerEventType.MiddleButtonDown, - 2 => RawPointerEventType.RightButtonDown, - 3 => RawPointerEventType.XButton1Down, - 4 => RawPointerEventType.XButton2Down, - // 5 => Pen eraser button, - _ => RawPointerEventType.Move + "touch" => RawPointerEventType.TouchBegin, + _ => args.GetPropertyAsInt32("button") switch + { + 0 => RawPointerEventType.LeftButtonDown, + 1 => RawPointerEventType.MiddleButtonDown, + 2 => RawPointerEventType.RightButtonDown, + 3 => RawPointerEventType.XButton1Down, + 4 => RawPointerEventType.XButton2Down, + 5 => RawPointerEventType.XButton1Down, // should be pen eraser button, + _ => RawPointerEventType.Move + } }; var point = ExtractRawPointerFromJSArgs(args); - - return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); + return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); } private bool OnPointerUp(JSObject args) { var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse"; - - if (pointerType == "touch") + var type = pointerType switch { - var x = args.GetPropertyAsDouble("clientX"); - var y = args.GetPropertyAsDouble("clientY"); - long identifier = args.GetPropertyAsInt32("identifier"); - - return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchEnd, new Point(x, y), - GetModifiers(args), identifier); - } - - var type = args.GetPropertyAsInt32("button") switch - { - 0 => RawPointerEventType.LeftButtonUp, - 1 => RawPointerEventType.MiddleButtonUp, - 2 => RawPointerEventType.RightButtonUp, - 3 => RawPointerEventType.XButton1Up, - 4 => RawPointerEventType.XButton2Up, - // 5 => Pen eraser button, - _ => RawPointerEventType.Move + "touch" => RawPointerEventType.TouchEnd, + _ => args.GetPropertyAsInt32("button") switch + { + 0 => RawPointerEventType.LeftButtonUp, + 1 => RawPointerEventType.MiddleButtonUp, + 2 => RawPointerEventType.RightButtonUp, + 3 => RawPointerEventType.XButton1Up, + 4 => RawPointerEventType.XButton2Up, + 5 => RawPointerEventType.XButton1Up, // should be pen eraser button, + _ => RawPointerEventType.Move + } }; var point = ExtractRawPointerFromJSArgs(args); - return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); } private bool OnPointerCancel(JSObject args) { var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse"; - if (pointerType == "touch") { - var x = args.GetPropertyAsDouble("clientX"); - var y = args.GetPropertyAsDouble("clientY"); - long identifier = args.GetPropertyAsInt32("identifier"); - - return _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchCancel, new Point(x, y), - GetModifiers(args), identifier); + var point = ExtractRawPointerFromJSArgs(args); + _topLevelImpl.RawPointerEvent(RawPointerEventType.TouchCancel, pointerType, point, + GetModifiers(args), args.GetPropertyAsInt32("pointerId")); } return false; diff --git a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs index 3c42deffd8..ed8f417870 100644 --- a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs +++ b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs @@ -64,35 +64,25 @@ namespace Avalonia.Web Resized?.Invoke(newSize, PlatformResizeReason.User); } } - - public bool RawTouchEvent(RawPointerEventType type, Point p, RawInputModifiers modifiers, long touchPointId) - { - if (_inputRoot is { } && Input is { } input) - { - var args = new RawTouchEventArgs(_touchDevice, Timestamp, _inputRoot, type, p, modifiers, - touchPointId); - - input.Invoke(args); - - return args.Handled; - } - - return false; - } public bool RawPointerEvent( RawPointerEventType eventType, string pointerType, - RawPointerPoint p, RawInputModifiers modifiers, long touchPointId) + RawPointerPoint p, RawInputModifiers modifiers, long touchPointId, + Lazy?>? intermediatePoints = null) { if (_inputRoot is { } && Input is { } input) { var device = GetPointerDevice(pointerType); var args = device is TouchDevice ? - new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId) : + new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId) + { + IntermediatePoints = intermediatePoints + } : new RawPointerEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers) { - RawPointerId = touchPointId + RawPointerId = touchPointId, + IntermediatePoints = intermediatePoints }; input.Invoke(args); diff --git a/src/Web/Avalonia.Web/Interop/InputHelper.cs b/src/Web/Avalonia.Web/Interop/InputHelper.cs index 0100701714..904fa915a8 100644 --- a/src/Web/Avalonia.Web/Interop/InputHelper.cs +++ b/src/Web/Avalonia.Web/Interop/InputHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using System.Runtime.InteropServices.JavaScript; using System.Threading.Tasks; @@ -47,6 +48,9 @@ internal static partial class InputHelper [JSMarshalAs>] Func input); + [JSImport("InputHelper.getCoalescedEvents", AvaloniaModule.MainModuleName)] + [return: JSMarshalAs>] + public static partial JSObject[] GetCoalescedEvents(JSObject pointerEvent); [JSImport("InputHelper.clearInput", AvaloniaModule.MainModuleName)] public static partial void ClearInputElement(JSObject htmlElement); diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts index f5a6c6e806..83e8ee7f1c 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts @@ -154,6 +154,10 @@ export class InputHelper { }; } + public static getCoalescedEvents(pointerEvent: PointerEvent): PointerEvent[] { + return pointerEvent.getCoalescedEvents(); + } + public static clearInput(inputElement: HTMLInputElement) { inputElement.value = ""; } From 572ad56150287c0d69db1c2fdb5ba11b3e406fea Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 25 Oct 2022 20:41:20 -0400 Subject: [PATCH 57/61] Complete Expander updates --- .../ControlCatalog/Pages/ExpanderPage.xaml | 17 +++++ .../Accents/FluentControlResourcesDark.xaml | 10 ++- .../Accents/FluentControlResourcesLight.xaml | 10 ++- .../Controls/Expander.xaml | 66 +++++++++++++++++-- 4 files changed, 93 insertions(+), 10 deletions(-) diff --git a/samples/ControlCatalog/Pages/ExpanderPage.xaml b/samples/ControlCatalog/Pages/ExpanderPage.xaml index f9ae1c7a3c..8c8702c665 100644 --- a/samples/ControlCatalog/Pages/ExpanderPage.xaml +++ b/samples/ControlCatalog/Pages/ExpanderPage.xaml @@ -32,6 +32,23 @@ Expanded content + + +