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 001/164] 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 002/164] 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 003/164] 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 004/164] 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 005/164] 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 006/164] 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 007/164] 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 14029cb934e7509e0b497de073fa680a1b4fc5a8 Mon Sep 17 00:00:00 2001 From: Arhell Date: Tue, 4 Oct 2022 01:05:27 +0300 Subject: [PATCH 008/164] update src folder links --- src/Avalonia.Base/Input/Cursor.cs | 2 +- src/Avalonia.Base/Media/PreciseEllipticArcHelper.cs | 2 +- .../Media/TextFormatting/Unicode/BinaryReaderExtensions.cs | 2 +- src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs | 2 +- src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs | 2 +- src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrie.cs | 2 +- .../TextFormatting/Unicode/UnicodeTrieBuilder.Constants.cs | 2 +- .../Media/TextFormatting/Unicode/UnicodeTrieBuilder.cs | 2 +- src/Avalonia.Base/Utilities/BinarySearchExtension.cs | 2 +- src/Avalonia.Controls/AutoCompleteBox.cs | 2 +- src/Avalonia.Controls/Calendar/Calendar.cs | 2 +- .../Calendar/CalendarBlackoutDatesCollection.cs | 2 +- src/Avalonia.Controls/Calendar/CalendarButton.cs | 2 +- src/Avalonia.Controls/Calendar/CalendarDateRange.cs | 2 +- src/Avalonia.Controls/Calendar/CalendarDayButton.cs | 2 +- src/Avalonia.Controls/Calendar/CalendarExtensions.cs | 2 +- src/Avalonia.Controls/Calendar/CalendarItem.cs | 2 +- src/Avalonia.Controls/Calendar/DateTimeHelper.cs | 2 +- src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs | 2 +- src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs | 2 +- .../CalendarDatePickerDateValidationErrorEventArgs.cs | 2 +- .../CalendarDatePicker/CalendarDatePickerFormat.cs | 2 +- .../Primitives/PopupPositioning/IPopupPositioner.cs | 2 +- src/Avalonia.Controls/Utils/ISelectionAdapter.cs | 2 +- .../Utils/SelectingItemsControlSelectionAdapter.cs | 2 +- src/Avalonia.Native/IconLoader.cs | 2 +- src/Avalonia.Themes.Fluent/Controls/Calendar.xaml | 2 +- src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml | 2 +- src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml | 2 +- src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml | 2 +- src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml | 2 +- src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml | 2 +- src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml | 2 +- src/Avalonia.Themes.Simple/Controls/CalendarItem.xaml | 2 +- src/Avalonia.Themes.Simple/Controls/DateTimePickerShared.xaml | 2 +- src/Avalonia.X11/X11Atoms.cs | 2 +- .../Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts | 2 +- 37 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/Avalonia.Base/Input/Cursor.cs b/src/Avalonia.Base/Input/Cursor.cs index 98c4258a90..8e79206f93 100644 --- a/src/Avalonia.Base/Input/Cursor.cs +++ b/src/Avalonia.Base/Input/Cursor.cs @@ -33,7 +33,7 @@ namespace Avalonia.Input DragLink, None, - // Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/ + // Not available in GTK directly, see https://www.pixelbeat.org/programming/x_cursors/ // We might enable them later, preferably, by loading pixmax directly from theme with fallback image // SizeNorthWestSouthEast, // SizeNorthEastSouthWest, diff --git a/src/Avalonia.Base/Media/PreciseEllipticArcHelper.cs b/src/Avalonia.Base/Media/PreciseEllipticArcHelper.cs index 5dd647e8ca..49dfe3c7b3 100644 --- a/src/Avalonia.Base/Media/PreciseEllipticArcHelper.cs +++ b/src/Avalonia.Base/Media/PreciseEllipticArcHelper.cs @@ -1081,7 +1081,7 @@ namespace Avalonia.Media Point c = rest * (cs) + translation; - // See "http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter" to understand + // See "https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter" to understand // how the ellipse center is calculated diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BinaryReaderExtensions.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BinaryReaderExtensions.cs index 412007c6e0..ccbceb6a7b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BinaryReaderExtensions.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BinaryReaderExtensions.cs @@ -5,7 +5,7 @@ // not use this product except in compliance with the License. You may obtain // a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs index ab17263806..ce9cdde044 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs @@ -104,7 +104,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// /// Gets the canonical representation of a given codepoint. - /// + /// /// /// The code point to be mapped. /// The mapped canonical code point, or the passed . diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs index 34b14f008f..59c4df0a2e 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs @@ -5,7 +5,7 @@ // not use this product except in compliance with the License. You may obtain // a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrie.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrie.cs index cf03ed7cd3..079f830ddc 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrie.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrie.cs @@ -5,7 +5,7 @@ // not use this product except in compliance with the License. You may obtain // a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.Constants.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.Constants.cs index 29ee45acc2..de0304f4c9 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.Constants.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.Constants.cs @@ -5,7 +5,7 @@ // not use this product except in compliance with the License. You may obtain // a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.cs index 87f96984c5..755d603539 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.cs @@ -5,7 +5,7 @@ // not use this product except in compliance with the License. You may obtain // a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT diff --git a/src/Avalonia.Base/Utilities/BinarySearchExtension.cs b/src/Avalonia.Base/Utilities/BinarySearchExtension.cs index a4f6ae89c1..b7060d2e21 100644 --- a/src/Avalonia.Base/Utilities/BinarySearchExtension.cs +++ b/src/Avalonia.Base/Utilities/BinarySearchExtension.cs @@ -5,7 +5,7 @@ // not use this product except in compliance with the License. You may obtain // a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index c675139831..8a8c4ead86 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs index 0de068a416..9c88bae5f6 100644 --- a/src/Avalonia.Controls/Calendar/Calendar.cs +++ b/src/Avalonia.Controls/Calendar/Calendar.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs index 5d883f2d14..a92feec509 100644 --- a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs +++ b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Calendar/CalendarButton.cs b/src/Avalonia.Controls/Calendar/CalendarButton.cs index 0a8e4dfae8..d8672cbf18 100644 --- a/src/Avalonia.Controls/Calendar/CalendarButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarButton.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using Avalonia.Controls.Metadata; diff --git a/src/Avalonia.Controls/Calendar/CalendarDateRange.cs b/src/Avalonia.Controls/Calendar/CalendarDateRange.cs index 88bc5ed7bd..793ef7a2ee 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDateRange.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDateRange.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs index 2ba4e36260..3d0befdba7 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Calendar/CalendarExtensions.cs b/src/Avalonia.Controls/Calendar/CalendarExtensions.cs index 00b5ce10bc..cb3ee06a9e 100644 --- a/src/Avalonia.Controls/Calendar/CalendarExtensions.cs +++ b/src/Avalonia.Controls/Calendar/CalendarExtensions.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using Avalonia.Input; diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index e2eabb5f28..eec3bdc9f2 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Calendar/DateTimeHelper.cs b/src/Avalonia.Controls/Calendar/DateTimeHelper.cs index 7a5c74a51b..bfff03a926 100644 --- a/src/Avalonia.Controls/Calendar/DateTimeHelper.cs +++ b/src/Avalonia.Controls/Calendar/DateTimeHelper.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs b/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs index f4bc2528ba..211b5edb0d 100644 --- a/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs +++ b/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using Avalonia.Threading; diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs index 3d592e9ab5..ec1273ca98 100644 --- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerDateValidationErrorEventArgs.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerDateValidationErrorEventArgs.cs index 647910cb6b..b58b549030 100644 --- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerDateValidationErrorEventArgs.cs +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerDateValidationErrorEventArgs.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerFormat.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerFormat.cs index 4d96859d75..ffd1f6f594 100644 --- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerFormat.cs +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerFormat.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. namespace Avalonia.Controls diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index d8de813d47..615eb69fe3 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs @@ -35,7 +35,7 @@ DEALINGS IN THE SOFTWARE. The above is the version of the MIT "Expat" License used by X.org: - http://cgit.freedesktop.org/xorg/xserver/tree/COPYING + https://cgit.freedesktop.org/xorg/xserver/tree/COPYING Adjustments for Avalonia needs: diff --git a/src/Avalonia.Controls/Utils/ISelectionAdapter.cs b/src/Avalonia.Controls/Utils/ISelectionAdapter.cs index c5fb12197f..3ede518ffa 100644 --- a/src/Avalonia.Controls/Utils/ISelectionAdapter.cs +++ b/src/Avalonia.Controls/Utils/ISelectionAdapter.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs b/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs index 0288f99dce..3c1b1262ae 100644 --- a/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs +++ b/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Native/IconLoader.cs b/src/Avalonia.Native/IconLoader.cs index edb8b94e83..04779a43aa 100644 --- a/src/Avalonia.Native/IconLoader.cs +++ b/src/Avalonia.Native/IconLoader.cs @@ -6,7 +6,7 @@ namespace Avalonia.Native // OSX doesn't have a concept of *window* icon. // Icons in the title bar are only shown if there is // an opened file (on disk) associated with the current window - // see http://stackoverflow.com/a/7038671/2231814 + // see https://stackoverflow.com/a/7038671/2231814 class IconLoader : IPlatformIconLoader { class IconStub : IWindowIconImpl diff --git a/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml b/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml index 7042f51c71..9c66ea9b84 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml @@ -1,7 +1,7 @@  diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml index d1aee7ee9a..06b6cf30c2 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml @@ -1,7 +1,7 @@  diff --git a/src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml b/src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml index be664b375d..7500ac7bca 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DateTimePickerShared.xaml @@ -1,7 +1,7 @@ diff --git a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml index fcd661a4b5..caf1251d25 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml @@ -1,7 +1,7 @@  diff --git a/src/Avalonia.Themes.Simple/Controls/CalendarItem.xaml b/src/Avalonia.Themes.Simple/Controls/CalendarItem.xaml index d9acd0d25a..2a9ae7cf8d 100644 --- a/src/Avalonia.Themes.Simple/Controls/CalendarItem.xaml +++ b/src/Avalonia.Themes.Simple/Controls/CalendarItem.xaml @@ -1,7 +1,7 @@ diff --git a/src/Avalonia.Themes.Simple/Controls/DateTimePickerShared.xaml b/src/Avalonia.Themes.Simple/Controls/DateTimePickerShared.xaml index 5909a1abbf..8639a2baa2 100644 --- a/src/Avalonia.Themes.Simple/Controls/DateTimePickerShared.xaml +++ b/src/Avalonia.Themes.Simple/Controls/DateTimePickerShared.xaml @@ -1,7 +1,7 @@ diff --git a/src/Avalonia.X11/X11Atoms.cs b/src/Avalonia.X11/X11Atoms.cs index 424db94e0a..b00879bd1d 100644 --- a/src/Avalonia.X11/X11Atoms.cs +++ b/src/Avalonia.X11/X11Atoms.cs @@ -17,7 +17,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -// Copyright (c) 2006 Novell, Inc. (http://www.novell.com) +// Copyright (c) 2006 Novell, Inc. (https://www.novell.com) // // diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts index 5709854087..60fd5a284e 100644 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts +++ b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts @@ -75,7 +75,7 @@ export class CaretHelper { div.textContent = element.value.substring(0, position); // The second special handling for input type="text" vs textarea: - // spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037 + // spaces need to be replaced with non-breaking spaces - https://stackoverflow.com/a/13402035/1269037 if (isInput) div.textContent = div.textContent.replace(/\s/g, "\u00a0"); const span = document.createElement("span"); From 4da0ae8dab4f844a9d7a4ddd6748c55aa6cee09e Mon Sep 17 00:00:00 2001 From: Arhell Date: Wed, 5 Oct 2022 01:10:26 +0300 Subject: [PATCH 009/164] update test folder links --- NOTICE.md | 4 ++-- .../Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs | 2 +- tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/NOTICE.md b/NOTICE.md index 92fd725957..e97fc654c9 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -111,7 +111,7 @@ DEALINGS IN THE SOFTWARE. # Metsys.Bson -Copyright (c) 2010, Karl Seguin - http://www.openmymind.net/ +Copyright (c) 2010, Karl Seguin - https://www.openmymind.net/ All rights reserved. Redistribution and use in source and binary forms, with or without @@ -302,4 +302,4 @@ https://github.com/chromium/chromium // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs index 1f0b82b465..26a1ab88c7 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs @@ -235,7 +235,7 @@ namespace Avalonia.Base.UnitTests.VisualTree public bool FillContains(Point point) { - // Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html + // Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html // to determine if the point is in the geometry (since it will always be convex in this situation) for (int i = 0; i < points.Count; i++) { diff --git a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs index 864e2efbaf..9d039a386e 100644 --- a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs +++ b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs @@ -148,7 +148,7 @@ namespace Avalonia.UnitTests public bool FillContains(Point point) { - // Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html + // Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html // to determine if the point is in the geometry (since it will always be convex in this situation) for (int i = 0; i < points.Count; i++) { From 61e15b3a8c5e4785ae0fced4be245d61f223a772 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 12:58:15 -0400 Subject: [PATCH 010/164] Create MaterialColorPalette.cs --- .../ColorPalettes/MaterialColorPalette.cs | 362 ++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs new file mode 100644 index 0000000000..4df1448f7a --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs @@ -0,0 +1,362 @@ +using Avalonia.Media; +using Avalonia.Utilities; + +namespace Avalonia.Controls +{ + /// + /// Implements a reduced version of the 2014 Material Design color palette. + /// + /// + /// This palette is based on the one outlined here: + /// + /// https://material.io/design/color/the-color-system.html#tools-for-picking-colors + /// + /// In order to make the palette uniform and rectangular the following + /// alterations were made: + /// + /// 1. The A100-A700 shades of each color are excluded. + /// These shades do not exist for all colors (brown/gray). + /// 2. Black/White are stand-alone and are also excluded. + /// + /// + public class MaterialColorPalette : IColorPalette + { + // See: https://material.io/design/color/the-color-system.html#tools-for-picking-colors + // This is a reduced palette for uniformity + private static Color[,]? _colorChart = null; + private static int _colorChartColorCount = 0; + private static int _colorChartShadeCount = 0; + private static object _colorChartMutex = new object(); + + /// + /// Initializes all color chart colors. + /// + /// + /// This is pulled out separately to lazy load for performance. + /// If no material color palette is ever used, no colors will be created. + /// + private void InitColorChart() + { + lock (_colorChartMutex) + { + _colorChart = new Color[,] + { + // Red + { + Color.FromArgb(0xFF, 0xFF, 0xEB, 0xEE), + Color.FromArgb(0xFF, 0xFF, 0xCD, 0xD2), + Color.FromArgb(0xFF, 0xEF, 0x9A, 0x9A), + Color.FromArgb(0xFF, 0xE5, 0x73, 0x73), + Color.FromArgb(0xFF, 0xEF, 0x53, 0x50), + Color.FromArgb(0xFF, 0xF4, 0x43, 0x36), + Color.FromArgb(0xFF, 0xE5, 0x39, 0x35), + Color.FromArgb(0xFF, 0xD3, 0x2F, 0x2F), + Color.FromArgb(0xFF, 0xC6, 0x28, 0x28), + Color.FromArgb(0xFF, 0xB7, 0x1C, 0x1C), + }, + + // Pink + { + Color.FromArgb(0xFF, 0xFC, 0xE4, 0xEC), + Color.FromArgb(0xFF, 0xF8, 0xBB, 0xD0), + Color.FromArgb(0xFF, 0xF4, 0x8F, 0xB1), + Color.FromArgb(0xFF, 0xF0, 0x62, 0x92), + Color.FromArgb(0xFF, 0xEC, 0x40, 0x7A), + Color.FromArgb(0xFF, 0xE9, 0x1E, 0x63), + Color.FromArgb(0xFF, 0xD8, 0x1B, 0x60), + Color.FromArgb(0xFF, 0xC2, 0x18, 0x5B), + Color.FromArgb(0xFF, 0xAD, 0x14, 0x57), + Color.FromArgb(0xFF, 0x88, 0x0E, 0x4F), + }, + + // Purple + { + Color.FromArgb(0xFF, 0xF3, 0xE5, 0xF5), + Color.FromArgb(0xFF, 0xE1, 0xBE, 0xE7), + Color.FromArgb(0xFF, 0xCE, 0x93, 0xD8), + Color.FromArgb(0xFF, 0xBA, 0x68, 0xC8), + Color.FromArgb(0xFF, 0xAB, 0x47, 0xBC), + Color.FromArgb(0xFF, 0x9C, 0x27, 0xB0), + Color.FromArgb(0xFF, 0x8E, 0x24, 0xAA), + Color.FromArgb(0xFF, 0x7B, 0x1F, 0xA2), + Color.FromArgb(0xFF, 0x6A, 0x1B, 0x9A), + Color.FromArgb(0xFF, 0x4A, 0x14, 0x8C), + }, + + // Deep Purple + { + Color.FromArgb(0xFF, 0xED, 0xE7, 0xF6), + Color.FromArgb(0xFF, 0xD1, 0xC4, 0xE9), + Color.FromArgb(0xFF, 0xB3, 0x9D, 0xDB), + Color.FromArgb(0xFF, 0x95, 0x75, 0xCD), + Color.FromArgb(0xFF, 0x7E, 0x57, 0xC2), + Color.FromArgb(0xFF, 0x67, 0x3A, 0xB7), + Color.FromArgb(0xFF, 0x5E, 0x35, 0xB1), + Color.FromArgb(0xFF, 0x51, 0x2D, 0xA8), + Color.FromArgb(0xFF, 0x45, 0x27, 0xA0), + Color.FromArgb(0xFF, 0x31, 0x1B, 0x92), + }, + + // Indigo + { + Color.FromArgb(0xFF, 0xE8, 0xEA, 0xF6), + Color.FromArgb(0xFF, 0xC5, 0xCA, 0xE9), + Color.FromArgb(0xFF, 0x9F, 0xA8, 0xDA), + Color.FromArgb(0xFF, 0x79, 0x86, 0xCB), + Color.FromArgb(0xFF, 0x5C, 0x6B, 0xC0), + Color.FromArgb(0xFF, 0x3F, 0x51, 0xB5), + Color.FromArgb(0xFF, 0x39, 0x49, 0xAB), + Color.FromArgb(0xFF, 0x30, 0x3F, 0x9F), + Color.FromArgb(0xFF, 0x28, 0x35, 0x93), + Color.FromArgb(0xFF, 0x1A, 0x23, 0x7E), + }, + + // Blue + { + Color.FromArgb(0xFF, 0xE3, 0xF2, 0xFD), + Color.FromArgb(0xFF, 0xBB, 0xDE, 0xFB), + Color.FromArgb(0xFF, 0x90, 0xCA, 0xF9), + Color.FromArgb(0xFF, 0x64, 0xB5, 0xF6), + Color.FromArgb(0xFF, 0x42, 0xA5, 0xF5), + Color.FromArgb(0xFF, 0x21, 0x96, 0xF3), + Color.FromArgb(0xFF, 0x1E, 0x88, 0xE5), + Color.FromArgb(0xFF, 0x19, 0x76, 0xD2), + Color.FromArgb(0xFF, 0x15, 0x65, 0xC0), + Color.FromArgb(0xFF, 0x0D, 0x47, 0xA1), + }, + + // Light Blue + { + Color.FromArgb(0xFF, 0xE1, 0xF5, 0xFE), + Color.FromArgb(0xFF, 0xB3, 0xE5, 0xFC), + Color.FromArgb(0xFF, 0x81, 0xD4, 0xFA), + Color.FromArgb(0xFF, 0x4F, 0xC3, 0xF7), + Color.FromArgb(0xFF, 0x29, 0xB6, 0xF6), + Color.FromArgb(0xFF, 0x03, 0xA9, 0xF4), + Color.FromArgb(0xFF, 0x03, 0x9B, 0xE5), + Color.FromArgb(0xFF, 0x02, 0x88, 0xD1), + Color.FromArgb(0xFF, 0x02, 0x77, 0xBD), + Color.FromArgb(0xFF, 0x01, 0x57, 0x9B), + }, + + // Cyan + { + Color.FromArgb(0xFF, 0xE0, 0xF7, 0xFA), + Color.FromArgb(0xFF, 0xB2, 0xEB, 0xF2), + Color.FromArgb(0xFF, 0x80, 0xDE, 0xEA), + Color.FromArgb(0xFF, 0x4D, 0xD0, 0xE1), + Color.FromArgb(0xFF, 0x26, 0xC6, 0xDA), + Color.FromArgb(0xFF, 0x00, 0xBC, 0xD4), + Color.FromArgb(0xFF, 0x00, 0xAC, 0xC1), + Color.FromArgb(0xFF, 0x00, 0x97, 0xA7), + Color.FromArgb(0xFF, 0x00, 0x83, 0x8F), + Color.FromArgb(0xFF, 0x00, 0x60, 0x64), + }, + + // Teal + { + Color.FromArgb(0xFF, 0xE0, 0xF2, 0xF1), + Color.FromArgb(0xFF, 0xB2, 0xDF, 0xDB), + Color.FromArgb(0xFF, 0x80, 0xCB, 0xC4), + Color.FromArgb(0xFF, 0x4D, 0xB6, 0xAC), + Color.FromArgb(0xFF, 0x26, 0xA6, 0x9A), + Color.FromArgb(0xFF, 0x00, 0x96, 0x88), + Color.FromArgb(0xFF, 0x00, 0x89, 0x7B), + Color.FromArgb(0xFF, 0x00, 0x79, 0x6B), + Color.FromArgb(0xFF, 0x00, 0x69, 0x5C), + Color.FromArgb(0xFF, 0x00, 0x4D, 0x40), + }, + + // Green + { + Color.FromArgb(0xFF, 0xE8, 0xF5, 0xE9), + Color.FromArgb(0xFF, 0xC8, 0xE6, 0xC9), + Color.FromArgb(0xFF, 0xA5, 0xD6, 0xA7), + Color.FromArgb(0xFF, 0x81, 0xC7, 0x84), + Color.FromArgb(0xFF, 0x66, 0xBB, 0x6A), + Color.FromArgb(0xFF, 0x4C, 0xAF, 0x50), + Color.FromArgb(0xFF, 0x43, 0xA0, 0x47), + Color.FromArgb(0xFF, 0x38, 0x8E, 0x3C), + Color.FromArgb(0xFF, 0x2E, 0x7D, 0x32), + Color.FromArgb(0xFF, 0x1B, 0x5E, 0x20), + }, + + // Light Green + { + Color.FromArgb(0xFF, 0xF1, 0xF8, 0xE9), + Color.FromArgb(0xFF, 0xDC, 0xED, 0xC8), + Color.FromArgb(0xFF, 0xC5, 0xE1, 0xA5), + Color.FromArgb(0xFF, 0xAE, 0xD5, 0x81), + Color.FromArgb(0xFF, 0x9C, 0xCC, 0x65), + Color.FromArgb(0xFF, 0x8B, 0xC3, 0x4A), + Color.FromArgb(0xFF, 0x7C, 0xB3, 0x42), + Color.FromArgb(0xFF, 0x68, 0x9F, 0x38), + Color.FromArgb(0xFF, 0x55, 0x8B, 0x2F), + Color.FromArgb(0xFF, 0x33, 0x69, 0x1E), + }, + + // Lime + { + Color.FromArgb(0xFF, 0xF9, 0xFB, 0xE7), + Color.FromArgb(0xFF, 0xF0, 0xF4, 0xC3), + Color.FromArgb(0xFF, 0xE6, 0xEE, 0x9C), + Color.FromArgb(0xFF, 0xDC, 0xE7, 0x75), + Color.FromArgb(0xFF, 0xD4, 0xE1, 0x57), + Color.FromArgb(0xFF, 0xCD, 0xDC, 0x39), + Color.FromArgb(0xFF, 0xC0, 0xCA, 0x33), + Color.FromArgb(0xFF, 0xAF, 0xB4, 0x2B), + Color.FromArgb(0xFF, 0x9E, 0x9D, 0x24), + Color.FromArgb(0xFF, 0x82, 0x77, 0x17), + }, + + // Yellow + { + Color.FromArgb(0xFF, 0xFF, 0xFD, 0xE7), + Color.FromArgb(0xFF, 0xFF, 0xF9, 0xC4), + Color.FromArgb(0xFF, 0xFF, 0xF5, 0x9D), + Color.FromArgb(0xFF, 0xFF, 0xF1, 0x76), + Color.FromArgb(0xFF, 0xFF, 0xEE, 0x58), + Color.FromArgb(0xFF, 0xFF, 0xEB, 0x3B), + Color.FromArgb(0xFF, 0xFD, 0xD8, 0x35), + Color.FromArgb(0xFF, 0xFB, 0xC0, 0x2D), + Color.FromArgb(0xFF, 0xF9, 0xA8, 0x25), + Color.FromArgb(0xFF, 0xF5, 0x7F, 0x17), + }, + + // Amber + { + Color.FromArgb(0xFF, 0xFF, 0xF8, 0xE1), + Color.FromArgb(0xFF, 0xFF, 0xEC, 0xB3), + Color.FromArgb(0xFF, 0xFF, 0xE0, 0x82), + Color.FromArgb(0xFF, 0xFF, 0xD5, 0x4F), + Color.FromArgb(0xFF, 0xFF, 0xCA, 0x28), + Color.FromArgb(0xFF, 0xFF, 0xC1, 0x07), + Color.FromArgb(0xFF, 0xFF, 0xB3, 0x00), + Color.FromArgb(0xFF, 0xFF, 0xA0, 0x00), + Color.FromArgb(0xFF, 0xFF, 0x8F, 0x00), + Color.FromArgb(0xFF, 0xFF, 0x6F, 0x00), + }, + + // Orange + { + Color.FromArgb(0xFF, 0xFF, 0xF3, 0xE0), + Color.FromArgb(0xFF, 0xFF, 0xE0, 0xB2), + Color.FromArgb(0xFF, 0xFF, 0xCC, 0x80), + Color.FromArgb(0xFF, 0xFF, 0xB7, 0x4D), + Color.FromArgb(0xFF, 0xFF, 0xA7, 0x26), + Color.FromArgb(0xFF, 0xFF, 0x98, 0x00), + Color.FromArgb(0xFF, 0xFB, 0x8C, 0x00), + Color.FromArgb(0xFF, 0xF5, 0x7C, 0x00), + Color.FromArgb(0xFF, 0xEF, 0x6C, 0x00), + Color.FromArgb(0xFF, 0xE6, 0x51, 0x00), + }, + + // Deep Orange + { + Color.FromArgb(0xFF, 0xFB, 0xE9, 0xE7), + Color.FromArgb(0xFF, 0xFF, 0xCC, 0xBC), + Color.FromArgb(0xFF, 0xFF, 0xAB, 0x91), + Color.FromArgb(0xFF, 0xFF, 0x8A, 0x65), + Color.FromArgb(0xFF, 0xFF, 0x70, 0x43), + Color.FromArgb(0xFF, 0xFF, 0x57, 0x22), + Color.FromArgb(0xFF, 0xF4, 0x51, 0x1E), + Color.FromArgb(0xFF, 0xE6, 0x4A, 0x19), + Color.FromArgb(0xFF, 0xD8, 0x43, 0x15), + Color.FromArgb(0xFF, 0xBF, 0x36, 0x0C), + }, + + // Brown + { + Color.FromArgb(0xFF, 0xEF, 0xEB, 0xE9), + Color.FromArgb(0xFF, 0xD7, 0xCC, 0xC8), + Color.FromArgb(0xFF, 0xBC, 0xAA, 0xA4), + Color.FromArgb(0xFF, 0xA1, 0x88, 0x7F), + Color.FromArgb(0xFF, 0x8D, 0x6E, 0x63), + Color.FromArgb(0xFF, 0x79, 0x55, 0x48), + Color.FromArgb(0xFF, 0x6D, 0x4C, 0x41), + Color.FromArgb(0xFF, 0x5D, 0x40, 0x37), + Color.FromArgb(0xFF, 0x4E, 0x34, 0x2E), + Color.FromArgb(0xFF, 0x3E, 0x27, 0x23), + }, + + // Gray + { + Color.FromArgb(0xFF, 0xFA, 0xFA, 0xFA), + Color.FromArgb(0xFF, 0xF5, 0xF5, 0xF5), + Color.FromArgb(0xFF, 0xEE, 0xEE, 0xEE), + Color.FromArgb(0xFF, 0xE0, 0xE0, 0xE0), + Color.FromArgb(0xFF, 0xBD, 0xBD, 0xBD), + Color.FromArgb(0xFF, 0x9E, 0x9E, 0x9E), + Color.FromArgb(0xFF, 0x75, 0x75, 0x75), + Color.FromArgb(0xFF, 0x61, 0x61, 0x61), + Color.FromArgb(0xFF, 0x42, 0x42, 0x42), + Color.FromArgb(0xFF, 0x21, 0x21, 0x21), + }, + + // Blue Gray + { + Color.FromArgb(0xFF, 0xEC, 0xEF, 0xF1), + Color.FromArgb(0xFF, 0xCF, 0xD8, 0xDC), + Color.FromArgb(0xFF, 0xB0, 0xBE, 0xC5), + Color.FromArgb(0xFF, 0x90, 0xA4, 0xAE), + Color.FromArgb(0xFF, 0x78, 0x90, 0x9C), + Color.FromArgb(0xFF, 0x60, 0x7D, 0x8B), + Color.FromArgb(0xFF, 0x54, 0x6E, 0x7A), + Color.FromArgb(0xFF, 0x45, 0x5A, 0x64), + Color.FromArgb(0xFF, 0x37, 0x47, 0x4F), + Color.FromArgb(0xFF, 0x26, 0x32, 0x38), + }, + }; + + _colorChartColorCount = _colorChart.GetLength(0); + _colorChartShadeCount = _colorChart.GetLength(1); + } + + return; + } + + /// + public int ColorCount + { + // Table is transposed compared to the reference chart + get + { + if (_colorChart == null) + { + InitColorChart(); + } + + return _colorChartColorCount; + } + } + + /// + public int ShadeCount + { + // Table is transposed compared to the reference chart + get + { + if (_colorChart == null) + { + InitColorChart(); + } + + return _colorChartShadeCount; + } + } + + /// + public Color GetColor(int colorIndex, int shadeIndex) + { + if (_colorChart == null) + { + InitColorChart(); + } + + // Table is transposed compared to the reference chart + return _colorChart![ + MathUtilities.Clamp(colorIndex, 0, _colorChartColorCount - 1), + MathUtilities.Clamp(shadeIndex, 0, _colorChartShadeCount - 1)]; + } + } +} From ea6c90b8fcd82487a5fafa8b66c0b9ac840072cd Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 13:04:15 -0400 Subject: [PATCH 011/164] Fix comments --- .../ColorPalettes/MaterialColorPalette.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs index 4df1448f7a..5cf5662ede 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs @@ -318,7 +318,6 @@ namespace Avalonia.Controls /// public int ColorCount { - // Table is transposed compared to the reference chart get { if (_colorChart == null) @@ -333,7 +332,6 @@ namespace Avalonia.Controls /// public int ShadeCount { - // Table is transposed compared to the reference chart get { if (_colorChart == null) @@ -353,7 +351,6 @@ namespace Avalonia.Controls InitColorChart(); } - // Table is transposed compared to the reference chart return _colorChart![ MathUtilities.Clamp(colorIndex, 0, _colorChartColorCount - 1), MathUtilities.Clamp(shadeIndex, 0, _colorChartShadeCount - 1)]; From 9250ccb18afb5d538b6e300a05c606da2d38d3a6 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 13:35:13 -0400 Subject: [PATCH 012/164] Add missing base constructor call --- .../ColorSpectrum/ColorSpectrum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index bd44161a42..200a652d3b 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -95,7 +95,7 @@ namespace Avalonia.Controls.Primitives /// /// Initializes a new instance of the class. /// - public ColorSpectrum() + public ColorSpectrum() : base() { _shapeFromLastBitmapCreation = Shape; _componentsFromLastBitmapCreation = Components; From 5c3a56a9533eab9060a216831df2a9a8c5e08e12 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 14:25:02 -0400 Subject: [PATCH 013/164] Fix the color spectrum selection ellipse position Specifically, when updated and not part of the visual tree --- .../ColorSpectrum/ColorSpectrum.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 200a652d3b..9261e5892d 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -171,6 +171,18 @@ namespace Avalonia.Controls.Primitives { base.OnAttachedToVisualTree(e); + // If the color was updated while this ColorSpectrum was not part of the visual tree, + // the selection ellipse may be in an incorrect position. This is because the spectrum + // renders based on layout scaling to avoid color banding; however, layout scale is only + // available when the control is attached to the visual tree. The ColorSpectrum's color + // may be updated from code-behind or from binding with another control when it's not + // part of the visual tree. + // + // See discussion: https://github.com/AvaloniaUI/Avalonia/discussions/9077 + // + // To work-around this issue the selection ellipse is refreshed here. + UpdateEllipse(); + // OnAttachedToVisualTree is called after OnApplyTemplate so events cannot be connected here } @@ -832,6 +844,8 @@ namespace Avalonia.Controls.Primitives } // Remember the bitmap size follows physical device pixels + // Warning: LayoutHelper.GetLayoutScale() doesn't work unless the control is visible + // This will not be true in all cases if the color is updated from another control or code-behind var scale = LayoutHelper.GetLayoutScale(this); Canvas.SetLeft(_selectionEllipsePanel, (xPosition / scale) - (_selectionEllipsePanel.Width / 2)); Canvas.SetTop(_selectionEllipsePanel, (yPosition / scale) - (_selectionEllipsePanel.Height / 2)); From 55e27213f958304679d449ef4aeb25ca9e8743ea Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 14:25:26 -0400 Subject: [PATCH 014/164] Comment additional ColorSpectrum methods --- .../ColorSpectrum/ColorSpectrum.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 9261e5892d..f272ce850e 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -600,6 +600,10 @@ namespace Avalonia.Controls.Primitives RaiseColorChanged(); } + /// + /// Updates the selected and based on a point within the color spectrum. + /// + /// The point on the spectrum representing the color. private void UpdateColorFromPoint(PointerPoint point) { // If we haven't initialized our HSV value array yet, then we should just ignore any user input - @@ -676,6 +680,9 @@ namespace Avalonia.Controls.Primitives UpdateColor(hsvAtPoint); } + /// + /// Updates the position of the selection ellipse on the spectrum which indicates the selected color. + /// private void UpdateEllipse() { if (_selectionEllipsePanel == null) From 30fd91121e638e0a835a305993793b4e709db51c Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 14:43:02 -0400 Subject: [PATCH 015/164] 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 016/164] 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 8b070919398878acda991efc0140f7a02cd2a8b7 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 9 Oct 2022 12:21:08 -0400 Subject: [PATCH 017/164] Fix formatting --- .../Themes/Fluent/ColorView.xaml | 30 +++++++++---------- .../Themes/Simple/ColorView.xaml | 30 +++++++++---------- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 16bc2acdd1..cf836aff4a 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -241,23 +241,21 @@ - + - + - + - + Date: Sun, 9 Oct 2022 13:51:06 -0400 Subject: [PATCH 018/164] Fix some template part names in selectors --- src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml | 6 +++--- src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml | 4 ++-- src/Avalonia.Themes.Simple/Controls/DatePicker.xaml | 2 +- src/Avalonia.Themes.Simple/Controls/TimePicker.xaml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml index 0cfd491b21..1c652ddc62 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml @@ -153,11 +153,11 @@ - - - diff --git a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml index fcd661a4b5..ba6dcb17ca 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml @@ -181,11 +181,11 @@ - - diff --git a/src/Avalonia.Themes.Simple/Controls/DatePicker.xaml b/src/Avalonia.Themes.Simple/Controls/DatePicker.xaml index 317a27435a..b4cf6313a6 100644 --- a/src/Avalonia.Themes.Simple/Controls/DatePicker.xaml +++ b/src/Avalonia.Themes.Simple/Controls/DatePicker.xaml @@ -170,7 +170,7 @@ - diff --git a/src/Avalonia.Themes.Simple/Controls/TimePicker.xaml b/src/Avalonia.Themes.Simple/Controls/TimePicker.xaml index 5aa717e9ee..e02e3d8194 100644 --- a/src/Avalonia.Themes.Simple/Controls/TimePicker.xaml +++ b/src/Avalonia.Themes.Simple/Controls/TimePicker.xaml @@ -189,7 +189,7 @@ - From 4b43cb3662e5f8b0f9e7b648dd2b445c405869c7 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 2 Oct 2022 11:51:25 -0400 Subject: [PATCH 019/164] Implement WriteableBitmap caching/reuse and disposal in ColorSlider --- .../ColorSlider/ColorSlider.cs | 42 ++++++++++++++++++- .../Helpers/ColorPickerHelpers.cs | 19 ++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index b662d20223..4e907f610d 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -2,6 +2,7 @@ using Avalonia.Controls.Metadata; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Media.Imaging; using Avalonia.Utilities; namespace Avalonia.Controls.Primitives @@ -31,6 +32,8 @@ namespace Avalonia.Controls.Primitives protected bool ignorePropertyChanged = false; + private WriteableBitmap? _backgroundBitmap; + /// /// Initializes a new instance of the class. /// @@ -38,6 +41,26 @@ namespace Avalonia.Controls.Primitives { } + /// + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + // Bitmaps were released when detached from the visual tree so they must be re-built + UpdateBackground(); + } + + /// + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + + // Clean-up all bitmaps + // https://github.com/AvaloniaUI/Avalonia/issues/9051 + _backgroundBitmap?.Dispose(); + _backgroundBitmap = null; + } + /// /// Updates the visual state of the control by applying latest PseudoClasses. /// @@ -110,7 +133,19 @@ namespace Avalonia.Controls.Primitives if (bitmap != null) { - Background = new ImageBrush(ColorPickerHelpers.CreateBitmapFromPixelData(bitmap, pixelWidth, pixelHeight)); + if (_backgroundBitmap != null) + { + // Re-use the existing WriteableBitmap + // This assumes the height, width and byte counts are the same and must be set to null + // elsewhere if that assumption is ever not true. + ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bitmap); + } + else + { + _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bitmap, pixelWidth, pixelHeight); + } + + Background = new ImageBrush(_backgroundBitmap); } } } @@ -399,6 +434,11 @@ namespace Avalonia.Controls.Primitives } else if (change.Property == BoundsProperty) { + // If the control's overall dimensions have changed the background bitmap size also needs to change. + // This means the existing bitmap must be released to be recreated correctly in UpdateBackground(). + _backgroundBitmap?.Dispose(); + _backgroundBitmap = null; + UpdateBackground(); } else if (change.Property == ValueProperty || diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index c1904a3c30..f2683e4677 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -617,7 +617,6 @@ namespace Avalonia.Controls.Primitives PixelFormat.Bgra8888, AlphaFormat.Premul); - // Warning: This is highly questionable using (var frameBuffer = bitmap.Lock()) { Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); @@ -625,5 +624,23 @@ namespace Avalonia.Controls.Primitives return bitmap; } + + /// + /// Updates the given with new, raw BGRA pre-multiplied alpha pixel data. + /// WARNING: The bitmap's width, height and byte count MUST not have changed and MUST be enforced externally. + /// + /// The existing to update. + /// The bitmap (in raw BGRA pre-multiplied alpha pixels). + public static void UpdateBitmapFromPixelData( + WriteableBitmap bitmap, + IList bgraPixelData) + { + using (var frameBuffer = bitmap.Lock()) + { + Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); + } + + return; + } } } From ca7543f1d613f9d7b9f2cfed978f8e23379d6684 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 2 Oct 2022 14:24:10 -0400 Subject: [PATCH 020/164] Add new ArrayList to avoid an extra copy when creating spectrum bitmaps --- .../ColorSlider/ColorSlider.cs | 8 +-- .../ColorSpectrum/ColorSpectrum.cs | 61 +++++++++------- .../Helpers/ArrayList.cs | 71 +++++++++++++++++++ .../Helpers/ColorPickerHelpers.cs | 18 ++--- 4 files changed, 118 insertions(+), 40 deletions(-) create mode 100644 src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 4e907f610d..8bcdef5558 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -121,7 +121,7 @@ namespace Avalonia.Controls.Primitives if (pixelWidth != 0 && pixelHeight != 0) { - var bitmap = await ColorPickerHelpers.CreateComponentBitmapAsync( + ArrayList bgraPixelData = await ColorPickerHelpers.CreateComponentBitmapAsync( pixelWidth, pixelHeight, Orientation, @@ -131,18 +131,18 @@ namespace Avalonia.Controls.Primitives IsAlphaMaxForced, IsSaturationValueMaxForced); - if (bitmap != null) + if (bgraPixelData != null) { if (_backgroundBitmap != null) { // Re-use the existing WriteableBitmap // This assumes the height, width and byte counts are the same and must be set to null // elsewhere if that assumption is ever not true. - ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bitmap); + ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData); } else { - _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bitmap, pixelWidth, pixelHeight); + _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight); } Background = new ImageBrush(_backgroundBitmap); diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index f272ce850e..3604894833 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -994,13 +994,13 @@ namespace Avalonia.Controls.Primitives // The middle 4 are only needed and used in the case of hue as the third dimension. // Saturation and luminosity need only a min and max. - List bgraMinPixelData = new List(); - List bgraMiddle1PixelData = new List(); - List bgraMiddle2PixelData = new List(); - List bgraMiddle3PixelData = new List(); - List bgraMiddle4PixelData = new List(); - List bgraMaxPixelData = new List(); - List newHsvValues = new List(); + ArrayList bgraMinPixelData; + ArrayList bgraMiddle1PixelData; + ArrayList bgraMiddle2PixelData; + ArrayList bgraMiddle3PixelData; + ArrayList bgraMiddle4PixelData; + ArrayList bgraMaxPixelData; + List newHsvValues; // In Avalonia, Bounds returns the actual device-independent pixel size of a control. // However, this is not necessarily the size of the control rendered on a display. @@ -1011,20 +1011,27 @@ namespace Avalonia.Controls.Primitives int pixelDimension = (int)Math.Round(minDimension * scale); var pixelCount = pixelDimension * pixelDimension; var pixelDataSize = pixelCount * 4; - bgraMinPixelData.Capacity = pixelDataSize; + + bgraMinPixelData = new ArrayList(pixelDataSize); + bgraMaxPixelData = new ArrayList(pixelDataSize); + newHsvValues = new List(pixelCount); // We'll only save pixel data for the middle bitmaps if our third dimension is hue. if (components == ColorSpectrumComponents.ValueSaturation || components == ColorSpectrumComponents.SaturationValue) { - bgraMiddle1PixelData.Capacity = pixelDataSize; - bgraMiddle2PixelData.Capacity = pixelDataSize; - bgraMiddle3PixelData.Capacity = pixelDataSize; - bgraMiddle4PixelData.Capacity = pixelDataSize; + bgraMiddle1PixelData = new ArrayList(pixelDataSize); + bgraMiddle2PixelData = new ArrayList(pixelDataSize); + bgraMiddle3PixelData = new ArrayList(pixelDataSize); + bgraMiddle4PixelData = new ArrayList(pixelDataSize); + } + else + { + bgraMiddle1PixelData = new ArrayList(0); + bgraMiddle2PixelData = new ArrayList(0); + bgraMiddle3PixelData = new ArrayList(0); + bgraMiddle4PixelData = new ArrayList(0); } - - bgraMaxPixelData.Capacity = pixelDataSize; - newHsvValues.Capacity = pixelCount; await Task.Run(() => { @@ -1132,12 +1139,12 @@ namespace Avalonia.Controls.Primitives double maxSaturation, double minValue, double maxValue, - List bgraMinPixelData, - List bgraMiddle1PixelData, - List bgraMiddle2PixelData, - List bgraMiddle3PixelData, - List bgraMiddle4PixelData, - List bgraMaxPixelData, + ArrayList bgraMinPixelData, + ArrayList bgraMiddle1PixelData, + ArrayList bgraMiddle2PixelData, + ArrayList bgraMiddle3PixelData, + ArrayList bgraMiddle4PixelData, + ArrayList bgraMaxPixelData, List newHsvValues) { double hMin = minHue; @@ -1292,12 +1299,12 @@ namespace Avalonia.Controls.Primitives double maxSaturation, double minValue, double maxValue, - List bgraMinPixelData, - List bgraMiddle1PixelData, - List bgraMiddle2PixelData, - List bgraMiddle3PixelData, - List bgraMiddle4PixelData, - List bgraMaxPixelData, + ArrayList bgraMinPixelData, + ArrayList bgraMiddle1PixelData, + ArrayList bgraMiddle2PixelData, + ArrayList bgraMiddle3PixelData, + ArrayList bgraMiddle4PixelData, + ArrayList bgraMaxPixelData, List newHsvValues) { double hMin = minHue; diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs new file mode 100644 index 0000000000..0b4c2f8579 --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ArrayList.cs @@ -0,0 +1,71 @@ +namespace Avalonia.Controls.Primitives +{ + /// + /// A thin wrapper over an that allows some additional list-like functionality. + /// + /// + /// This is only for internal ColorPicker-related functionality and should not be used elsewhere. + /// It is added for performance to enjoy the simplicity of the IList.Add() method without requiring + /// an additional copy to turn a list into an array for bitmaps. + /// + /// The type of items in the array. + internal class ArrayList + { + private int _nextIndex = 0; + + /// + /// Initializes a new instance of the class. + /// + public ArrayList(int capacity) + { + Capacity = capacity; + Array = new T[capacity]; + } + + /// + /// Provides access to the underlying array by index. + /// This exists for simplification and the property + /// may also be used. + /// + /// The index of the item to get or set. + /// The item at the given index. + public T this[int i] + { + get => Array[i]; + set => Array[i] = value; + } + + /// + /// Gets the underlying array. + /// + public T[] Array { get; private set; } + + /// + /// Gets the fixed capacity/size of the array. + /// This must be set during construction. + /// + public int Capacity { get; private set; } + + /// + /// Adds the given item to the array at the next available index. + /// WARNING: This must be used carefully and only once, in sequence. + /// + /// The item to add. + public void Add(T item) + { + if (_nextIndex >= 0 && + _nextIndex < Capacity) + { + Array[_nextIndex] = item; + _nextIndex++; + } + else + { + // If necessary an exception could be thrown here + // throw new IndexOutOfRangeException(); + } + + return; + } + } +} diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index f2683e4677..2378813f52 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -37,7 +37,7 @@ namespace Avalonia.Controls.Primitives /// during calculation with the HSVA color model. /// This will ensure colors are always discernible regardless of saturation/value. /// A new bitmap representing a gradient of color component values. - public static async Task CreateComponentBitmapAsync( + public static async Task> CreateComponentBitmapAsync( int width, int height, Orientation orientation, @@ -49,14 +49,14 @@ namespace Avalonia.Controls.Primitives { if (width == 0 || height == 0) { - return new byte[0]; + return new ArrayList(0); } - var bitmap = await Task.Run(() => + var bitmap = await Task.Run>(() => { int pixelDataIndex = 0; double componentStep; - byte[] bgraPixelData; + ArrayList bgraPixelData; Color baseRgbColor = Colors.White; Color rgbColor; int bgraPixelDataHeight; @@ -64,7 +64,7 @@ namespace Avalonia.Controls.Primitives // Allocate the buffer // BGRA formatted color components 1 byte each (4 bytes in a pixel) - bgraPixelData = new byte[width * height * 4]; + bgraPixelData = new ArrayList(width * height * 4); bgraPixelDataHeight = height * 4; bgraPixelDataWidth = width * 4; @@ -604,7 +604,7 @@ namespace Avalonia.Controls.Primitives /// The pixel height of the bitmap. /// A new . public static WriteableBitmap CreateBitmapFromPixelData( - IList bgraPixelData, + ArrayList bgraPixelData, int pixelWidth, int pixelHeight) { @@ -619,7 +619,7 @@ namespace Avalonia.Controls.Primitives using (var frameBuffer = bitmap.Lock()) { - Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); + Marshal.Copy(bgraPixelData.Array, 0, frameBuffer.Address, bgraPixelData.Array.Length); } return bitmap; @@ -633,11 +633,11 @@ namespace Avalonia.Controls.Primitives /// The bitmap (in raw BGRA pre-multiplied alpha pixels). public static void UpdateBitmapFromPixelData( WriteableBitmap bitmap, - IList bgraPixelData) + ArrayList bgraPixelData) { using (var frameBuffer = bitmap.Lock()) { - Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); + Marshal.Copy(bgraPixelData.Array, 0, frameBuffer.Address, bgraPixelData.Array.Length); } return; From 98a58217ce22d55f9343216fd4fbc727618c7306 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 2 Oct 2022 14:25:24 -0400 Subject: [PATCH 021/164] Fix ColorSlider update/refresh for all property changes --- .../ColorSlider/ColorSlider.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 8bcdef5558..6f20238b70 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -385,11 +385,11 @@ namespace Avalonia.Controls.Primitives return; } - // Always keep the two color properties in sync if (change.Property == ColorProperty) { ignorePropertyChanged = true; + // Always keep the two color properties in sync HsvColor = Color.ToHsv(); SetColorToSliderValues(); @@ -402,7 +402,10 @@ namespace Avalonia.Controls.Primitives ignorePropertyChanged = false; } - else if (change.Property == ColorModelProperty) + else if (change.Property == ColorComponentProperty || + change.Property == ColorModelProperty || + change.Property == IsAlphaMaxForcedProperty || + change.Property == IsSaturationValueMaxForcedProperty) { ignorePropertyChanged = true; @@ -416,6 +419,7 @@ namespace Avalonia.Controls.Primitives { ignorePropertyChanged = true; + // Always keep the two color properties in sync Color = HsvColor.ToRgb(); SetColorToSliderValues(); @@ -440,6 +444,7 @@ namespace Avalonia.Controls.Primitives _backgroundBitmap = null; UpdateBackground(); + UpdatePseudoClasses(); } else if (change.Property == ValueProperty || change.Property == MinimumProperty || From 4f1d315b4b11cf84ca7c3b4ffc65c41d35e24436 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 2 Oct 2022 14:26:28 -0400 Subject: [PATCH 022/164] Make all ColorSpectrum bitmaps globally accessible by the control for future disposal --- .../ColorSpectrum/ColorSpectrum.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 3604894833..6b06e1eac1 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -73,6 +73,8 @@ namespace Avalonia.Controls.Primitives private WriteableBitmap? _saturationMaximumBitmap; private WriteableBitmap? _valueBitmap; + private WriteableBitmap? _minBitmap; + private WriteableBitmap? _maxBitmap; // Fields used by UpdateEllipse() to ensure that it's using the data // associated with the last call to CreateBitmapsAndColorMap(), @@ -1084,28 +1086,28 @@ namespace Avalonia.Controls.Primitives ColorSpectrumComponents components2 = Components; - WriteableBitmap minBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMinPixelData, pixelWidth, pixelHeight); - WriteableBitmap maxBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMaxPixelData, pixelWidth, pixelHeight); + _minBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMinPixelData, pixelWidth, pixelHeight); + _maxBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMaxPixelData, pixelWidth, pixelHeight); switch (components2) { case ColorSpectrumComponents.HueValue: case ColorSpectrumComponents.ValueHue: - _saturationMinimumBitmap = minBitmap; - _saturationMaximumBitmap = maxBitmap; + _saturationMinimumBitmap = _minBitmap; + _saturationMaximumBitmap = _maxBitmap; break; case ColorSpectrumComponents.HueSaturation: case ColorSpectrumComponents.SaturationHue: - _valueBitmap = maxBitmap; + _valueBitmap = _maxBitmap; break; case ColorSpectrumComponents.ValueSaturation: case ColorSpectrumComponents.SaturationValue: - _hueRedBitmap = minBitmap; + _hueRedBitmap = _minBitmap; _hueYellowBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle1PixelData, pixelWidth, pixelHeight); _hueGreenBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle2PixelData, pixelWidth, pixelHeight); _hueCyanBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle3PixelData, pixelWidth, pixelHeight); _hueBlueBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraMiddle4PixelData, pixelWidth, pixelHeight); - _huePurpleBitmap = maxBitmap; + _huePurpleBitmap = _maxBitmap; break; } From 667b02d2c0d6e713980f0ad05c843d96e0c5a6f4 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 2 Oct 2022 14:33:24 -0400 Subject: [PATCH 023/164] Disable WriteableBitmap re-use in ColorSlider due to crashes --- .../ColorSlider/ColorSlider.cs | 8 +++++++- .../Helpers/ColorPickerHelpers.cs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 6f20238b70..8b624958dd 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -135,10 +135,16 @@ namespace Avalonia.Controls.Primitives { if (_backgroundBitmap != null) { + // CURRENTLY DISABLED DUE TO INTERMITTENT CRASHES IN SKIA/RENDERER + // // Re-use the existing WriteableBitmap // This assumes the height, width and byte counts are the same and must be set to null // elsewhere if that assumption is ever not true. - ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData); + // ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData); + + // ALSO DISABLED DISPOSE DUE TO INTERMITTENT CRASHES + //_backgroundBitmap?.Dispose(); + _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight); } else { diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index 2378813f52..01e5a0868f 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -627,6 +627,7 @@ namespace Avalonia.Controls.Primitives /// /// Updates the given with new, raw BGRA pre-multiplied alpha pixel data. + /// WARNING: THIS METHOD IS CURRENTLY PROVIDED AS REFERENCE BUT CAUSES INTERMITTENT CRASHES IF USED. /// WARNING: The bitmap's width, height and byte count MUST not have changed and MUST be enforced externally. /// /// The existing to update. From bc927d03129fa201248d2bcab78143f7a1c8f93f Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 9 Oct 2022 21:07:58 -0400 Subject: [PATCH 024/164] Remove ColorSlider background disposal on attach/detach from visual tree --- .../ColorSlider/ColorSlider.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 8b624958dd..0b9f7f9146 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -45,20 +45,12 @@ namespace Avalonia.Controls.Primitives protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); - - // Bitmaps were released when detached from the visual tree so they must be re-built - UpdateBackground(); } /// protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); - - // Clean-up all bitmaps - // https://github.com/AvaloniaUI/Avalonia/issues/9051 - _backgroundBitmap?.Dispose(); - _backgroundBitmap = null; } /// From 0d05c40ca25381cec24a1c6456891113794d7b6a Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 10 Oct 2022 04:05:14 -0400 Subject: [PATCH 025/164] Remove old blazor backend. Keep blazor components with new WASM implementation. --- samples/ControlCatalog.Web/App.razor.cs | 4 - .../ControlCatalog.Web.csproj | 40 +- .../ControlCatalog.Web/EmbedSample.Browser.cs | 34 -- ...valonia.Web.Blazor.CompilationTuning.props | 7 - .../Avalonia.Web.Blazor.csproj | 53 +- .../Avalonia.Web.Blazor.props | 4 - .../Avalonia.Web.Blazor.targets | 6 - .../AvaloniaBlazorAppBuilder.cs | 20 - src/Web/Avalonia.Web.Blazor/AvaloniaView.cs | 43 ++ .../Avalonia.Web.Blazor/AvaloniaView.razor | 67 --- .../Avalonia.Web.Blazor/AvaloniaView.razor.cs | 500 ------------------ .../BlazorSingleViewLifetime.cs | 35 +- src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs | 25 - .../BlazorSkiaGpuRenderSession.cs | 37 -- .../BlazorSkiaGpuRenderTarget.cs | 39 -- .../BlazorSkiaRasterSurface.cs | 87 --- .../Avalonia.Web.Blazor/BlazorSkiaSurface.cs | 30 -- src/Web/Avalonia.Web.Blazor/ClipboardImpl.cs | 34 -- src/Web/Avalonia.Web.Blazor/Cursor.cs | 93 ---- .../Avalonia.Web.Blazor/IBlazorSkiaSurface.cs | 9 - .../Interop/ActionHelper.cs | 81 --- .../Interop/AvaloniaModule.cs | 18 - .../Interop/DpiWatcherInterop.cs | 72 --- .../Interop/FocusHelperInterop.cs | 22 - .../Interop/InputHelperInterop.cs | 130 ----- .../Interop/JSModuleInterop.cs | 46 -- .../Interop/NativeControlHostImpl.cs | 144 ----- .../Interop/SKHtmlCanvasInterop.cs | 76 --- .../Interop/SizeWatcherInterop.cs | 50 -- .../Interop/Storage/StorageProviderInterop.cs | 225 -------- .../Interop/Storage/WriteableStream.cs | 124 ----- .../JSObjectControlHandle.cs | 35 -- src/Web/Avalonia.Web.Blazor/Keycodes.cs | 127 ----- .../ManualTriggerRenderTimer.cs | 17 - .../RazorViewTopLevelImpl.cs | 222 -------- src/Web/Avalonia.Web.Blazor/WinStubs.cs | 50 -- .../Avalonia.Web.Blazor/WindowingPlatform.cs | 106 ---- src/Web/Avalonia.Web.Blazor/_Imports.razor | 1 - src/Web/Avalonia.Web.Blazor/webapp/build.js | 16 - .../webapp/modules/Avalonia.ts | 7 - .../webapp/modules/Avalonia/CaretHelper.ts | 149 ------ .../webapp/modules/Avalonia/DpiWatcher.ts | 40 -- .../webapp/modules/Avalonia/FocusHelper.ts | 9 - .../webapp/modules/Avalonia/InputHelper.ts | 86 --- .../modules/Avalonia/NativeControlHost.ts | 61 --- .../webapp/modules/Avalonia/SKHtmlCanvas.ts | 255 --------- .../webapp/modules/Avalonia/SizeWatcher.ts | 67 --- .../webapp/modules/Storage.ts | 1 - .../modules/Storage/IndexedDbWrapper.ts | 79 --- .../webapp/modules/Storage/StorageProvider.ts | 204 ------- .../Avalonia.Web.Blazor/webapp/package.json | 13 - .../Avalonia.Web.Blazor/webapp/tsconfig.json | 18 - .../webapp/types/dotnet/index.d.ts | 56 -- src/Web/Avalonia.Web.Sample/main.js | 4 +- src/Web/Avalonia.Web/AvaloniaView.cs | 19 +- .../Avalonia.Web/BrowserSingleViewLifetime.cs | 22 +- src/Web/Avalonia.Web/Cursor.cs | 2 +- src/Web/Avalonia.Web/Interop/CanvasHelper.cs | 6 +- src/Web/Avalonia.Web/Interop/DomHelper.cs | 10 +- src/Web/Avalonia.Web/Interop/InputHelper.cs | 24 +- .../Interop/NativeControlHostHelper.cs | 14 +- src/Web/Avalonia.Web/Interop/StorageHelper.cs | 30 +- src/Web/Avalonia.Web/Interop/StreamHelper.cs | 14 +- .../Avalonia.Web/ManualTriggerRenderTimer.cs | 2 +- .../Storage/BrowserStorageProvider.cs | 2 +- src/Web/Avalonia.Web/WindowingPlatform.cs | 2 +- .../Avalonia.Web/webapp/modules/avalonia.ts | 26 +- .../webapp/modules/avalonia/canvas.ts | 4 +- 68 files changed, 182 insertions(+), 3773 deletions(-) delete mode 100644 samples/ControlCatalog.Web/EmbedSample.Browser.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props delete mode 100644 src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props delete mode 100644 src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.targets delete mode 100644 src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs create mode 100644 src/Web/Avalonia.Web.Blazor/AvaloniaView.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/AvaloniaView.razor delete mode 100644 src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderSession.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderTarget.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/ClipboardImpl.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Cursor.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/ActionHelper.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/AvaloniaModule.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/FocusHelperInterop.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/NativeControlHostImpl.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Interop/Storage/WriteableStream.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/JSObjectControlHandle.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/Keycodes.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/ManualTriggerRenderTimer.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/WinStubs.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs delete mode 100644 src/Web/Avalonia.Web.Blazor/_Imports.razor delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/build.js delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/DpiWatcher.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/FocusHelper.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/NativeControlHost.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SKHtmlCanvas.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SizeWatcher.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Storage.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/IndexedDbWrapper.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/package.json delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/tsconfig.json delete mode 100644 src/Web/Avalonia.Web.Blazor/webapp/types/dotnet/index.d.ts diff --git a/samples/ControlCatalog.Web/App.razor.cs b/samples/ControlCatalog.Web/App.razor.cs index bcd2a6fefc..ca3d3604b1 100644 --- a/samples/ControlCatalog.Web/App.razor.cs +++ b/samples/ControlCatalog.Web/App.razor.cs @@ -8,10 +8,6 @@ public partial class App protected override void OnParametersSet() { WebAppBuilder.Configure() - .AfterSetup(_ => - { - ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb(); - }) .With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering .SetupWithSingleViewLifetime(); diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj index b2c9ec72eb..03fb31f0d3 100644 --- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj +++ b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj @@ -1,44 +1,16 @@  - net6.0 + net7.0 + browser-wasm enable - - true 16777216 false false - - - false - -O1 - false - - - - true - true - -O3 - -O3 - false - false - false - false - false - false - true - false - true - true - true - link - true - - - - + + @@ -50,8 +22,8 @@ - - + + diff --git a/samples/ControlCatalog.Web/EmbedSample.Browser.cs b/samples/ControlCatalog.Web/EmbedSample.Browser.cs deleted file mode 100644 index 5fe14409de..0000000000 --- a/samples/ControlCatalog.Web/EmbedSample.Browser.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -using Avalonia; -using Avalonia.Platform; -using Avalonia.Web.Blazor; - -using ControlCatalog.Pages; - -using Microsoft.JSInterop; - -namespace ControlCatalog.Web; - -public class EmbedSampleWeb : INativeDemoControl -{ - public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func createDefault) - { - var runtime = AvaloniaLocator.Current.GetRequiredService(); - - if (isSecond) - { - var iframe = runtime.Invoke("document.createElement", "iframe"); - iframe.InvokeVoid("setAttribute", "src", "https://www.youtube.com/embed/kZCIporjJ70"); - - return new JSObjectControlHandle(iframe); - } - else - { - // window.createAppButton source is defined in "app.js" file. - var button = runtime.Invoke("window.createAppButton"); - - return new JSObjectControlHandle(button); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props deleted file mode 100644 index eb5e5dd733..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props +++ /dev/null @@ -1,7 +0,0 @@ - - - 16777216 - false - false - - diff --git a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj index 693a6a1462..1c31e0eb5d 100644 --- a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj +++ b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj @@ -1,53 +1,40 @@  - net6.0 - enable + net7.0 Avalonia.Web.Blazor - preview - false - true + _IncludeGeneratedAvaloniaStaticFiles;$(ResolveStaticWebAssetsInputsDependsOn) - - - - - true - build\ - - - true - build\;buildTransitive\ - + - - - - - - - - - - + - - - - - - - + + + <_AvaloniaWebAssets Include="$(MSBuildThisFileDirectory)../Avalonia.Web/wwwroot/**/*.*" /> + + + + diff --git a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props deleted file mode 100644 index dd96a60c6a..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.targets b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.targets deleted file mode 100644 index e9052fda88..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.targets +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs deleted file mode 100644 index 11d9bcc98f..0000000000 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Platform; - -namespace Avalonia.Web.Blazor -{ - public class AvaloniaBlazorAppBuilder : AppBuilderBase - { - public AvaloniaBlazorAppBuilder(IRuntimePlatform platform, Action platformServices) - : base(platform, platformServices) - { - } - - public AvaloniaBlazorAppBuilder() - : base(new StandardRuntimePlatform(), - builder => StandardRuntimePlatformServices.Register(builder.ApplicationType!.Assembly)) - { - UseWindowingSubsystem(BlazorWindowingPlatform.Register); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs new file mode 100644 index 0000000000..ae294151a6 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs @@ -0,0 +1,43 @@ +using System.Runtime.InteropServices.JavaScript; +using System.Runtime.Versioning; +using System.Threading.Tasks; +using System; +using Avalonia.Controls.ApplicationLifetimes; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using BrowserView = Avalonia.Web.AvaloniaView; + +namespace Avalonia.Web.Blazor; + +[SupportedOSPlatform("browser")] +public class AvaloniaView : ComponentBase +{ + private BrowserView? _browserView; + private readonly string _containerId; + + public AvaloniaView() + { + _containerId = "av_container_" + Guid.NewGuid(); + } + + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.OpenElement(0, "div"); + builder.AddAttribute(1, "id", _containerId); + builder.CloseElement(); + } + + protected override async Task OnInitializedAsync() + { + if (OperatingSystem.IsBrowser()) + { + _ = await JSHost.ImportAsync("avalonia", "/_content/Avalonia.Web.Blazor/avalonia.js"); + + _browserView = new BrowserView(_containerId); + if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime lifetime) + { + _browserView.Content = lifetime.MainView; + } + } + } +} diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor deleted file mode 100644 index 5a3b9d5f71..0000000000 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor +++ /dev/null @@ -1,67 +0,0 @@ -
- - - -
- - -
- - diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs deleted file mode 100644 index 0e64e98f1e..0000000000 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ /dev/null @@ -1,500 +0,0 @@ -using System.Diagnostics; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Controls.Embedding; -using Avalonia.Controls.Platform; -using Avalonia.Input; -using Avalonia.Input.Raw; -using Avalonia.Input.TextInput; -using Avalonia.Platform.Storage; -using Avalonia.Rendering; -using Avalonia.Rendering.Composition; -using Avalonia.Web.Blazor.Interop; -using Avalonia.Web.Blazor.Interop.Storage; - -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Forms; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.JSInterop; - -using SkiaSharp; -using HTMLPointerEventArgs = Microsoft.AspNetCore.Components.Web.PointerEventArgs; - -namespace Avalonia.Web.Blazor -{ - public partial class AvaloniaView : ITextInputMethodImpl - { - private readonly RazorViewTopLevelImpl _topLevelImpl; - private EmbeddableControlRoot _topLevel; - - // Interop - private SKHtmlCanvasInterop? _interop = null; - private SizeWatcherInterop? _sizeWatcher = null; - private DpiWatcherInterop? _dpiWatcher = null; - private SKHtmlCanvasInterop.GLInfo? _jsGlInfo = null; - private AvaloniaModule? _avaloniaModule = null; - private InputHelperInterop? _inputHelper = null; - private FocusHelperInterop? _canvasHelper = null; - private FocusHelperInterop? _containerHelper = null; - private NativeControlHostInterop? _nativeControlHost = null; - private StorageProviderInterop? _storageProvider = null; - private ElementReference _htmlCanvas; - private ElementReference _inputElement; - private ElementReference _containerElement; - private ElementReference _nativeControlsContainer; - private double _dpi = 1; - private SKSize _canvasSize = new (100, 100); - - private GRContext? _context; - private GRGlInterface? _glInterface; - private const SKColorType ColorType = SKColorType.Rgba8888; - - private bool _useGL; - private bool _inputElementFocused; - - private ITextInputMethodClient? _client; - - - [Inject] private IJSRuntime Js { get; set; } = null!; - - public AvaloniaView() - { - _topLevelImpl = new RazorViewTopLevelImpl(this); - - _topLevel = new EmbeddableControlRoot(_topLevelImpl); - - if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime lifetime) - { - _topLevel.Content = lifetime.MainView; - } - } - - public bool KeyPreventDefault { get; set; } - - public ITextInputMethodClient? Client => _client; - - public bool IsActive => _client != null; - - public bool IsComposing { get; private set; } - - internal INativeControlHostImpl GetNativeControlHostImpl() - { - return _nativeControlHost ?? throw new InvalidOperationException("Blazor View wasn't initialized yet"); - } - - internal IStorageProvider GetStorageProvider() - { - return _storageProvider ?? throw new InvalidOperationException("Blazor View wasn't initialized yet"); - } - - private void OnPointerCancel(HTMLPointerEventArgs e) - { - if (e.PointerType == "touch") - { - _topLevelImpl.RawPointerEvent(RawPointerEventType.TouchCancel, e.PointerType, GetPointFromEventArgs(e), - GetModifiers(e), e.PointerId); - } - } - - private void OnPointerMove(HTMLPointerEventArgs e) - { - var type = e.PointerType switch - { - "touch" => RawPointerEventType.TouchUpdate, - _ => RawPointerEventType.Move - }; - - _topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId); - } - - private void OnPointerUp(HTMLPointerEventArgs e) - { - var type = e.PointerType switch - { - "touch" => RawPointerEventType.TouchEnd, - _ => e.Button switch - { - 0 => RawPointerEventType.LeftButtonUp, - 1 => RawPointerEventType.MiddleButtonUp, - 2 => RawPointerEventType.RightButtonUp, - 3 => RawPointerEventType.XButton1Up, - 4 => RawPointerEventType.XButton2Up, - // 5 => Pen eraser button, - _ => RawPointerEventType.Move - } - }; - - _topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId); - } - - private void OnPointerDown(HTMLPointerEventArgs e) - { - var type = e.PointerType switch - { - "touch" => RawPointerEventType.TouchBegin, - _ => e.Button switch - { - 0 => RawPointerEventType.LeftButtonDown, - 1 => RawPointerEventType.MiddleButtonDown, - 2 => RawPointerEventType.RightButtonDown, - 3 => RawPointerEventType.XButton1Down, - 4 => RawPointerEventType.XButton2Down, - // 5 => Pen eraser button, - _ => RawPointerEventType.Move - } - }; - - _topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId); - } - - private static RawPointerPoint GetPointFromEventArgs(HTMLPointerEventArgs args) - { - return new RawPointerPoint - { - Position = new Point(args.ClientX, args.ClientY), - Pressure = args.Pressure, - XTilt = args.TiltX, - YTilt = args.TiltY - // Twist = args.Twist - read from JS code directly when - }; - } - - private void OnWheel(WheelEventArgs e) - { - _topLevelImpl.RawMouseWheelEvent( new Point(e.ClientX, e.ClientY), - new Vector(-(e.DeltaX / 50), -(e.DeltaY / 50)), GetModifiers(e)); - } - - private static RawInputModifiers GetModifiers(MouseEventArgs e) - { - var modifiers = RawInputModifiers.None; - - if (e.CtrlKey) - modifiers |= RawInputModifiers.Control; - if (e.AltKey) - modifiers |= RawInputModifiers.Alt; - if (e.ShiftKey) - modifiers |= RawInputModifiers.Shift; - if (e.MetaKey) - modifiers |= RawInputModifiers.Meta; - - if ((e.Buttons & 1L) == 1) - modifiers |= RawInputModifiers.LeftMouseButton; - - if ((e.Buttons & 2L) == 2) - modifiers |= e.Type == "pen" ? RawInputModifiers.PenBarrelButton : RawInputModifiers.RightMouseButton; - - if ((e.Buttons & 4L) == 4) - modifiers |= RawInputModifiers.MiddleMouseButton; - - if ((e.Buttons & 8L) == 8) - modifiers |= RawInputModifiers.XButton1MouseButton; - - if ((e.Buttons & 16L) == 16) - modifiers |= RawInputModifiers.XButton2MouseButton; - - if ((e.Buttons & 32L) == 32) - modifiers |= RawInputModifiers.PenEraser; - - return modifiers; - } - - private static RawInputModifiers GetModifiers(KeyboardEventArgs e) - { - var modifiers = RawInputModifiers.None; - - if (e.CtrlKey) - modifiers |= RawInputModifiers.Control; - if (e.AltKey) - modifiers |= RawInputModifiers.Alt; - if (e.ShiftKey) - modifiers |= RawInputModifiers.Shift; - if (e.MetaKey) - modifiers |= RawInputModifiers.Meta; - - return modifiers; - } - - private void OnKeyDown(KeyboardEventArgs e) - { - KeyPreventDefault = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, e.Code, e.Key, GetModifiers(e)); - } - - private void OnKeyUp(KeyboardEventArgs e) - { - KeyPreventDefault = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyUp, e.Code, e.Key, GetModifiers(e)); - } - - private void OnFocus(FocusEventArgs e) - { - // if focus has unexpectedly moved from the input element to the container element, - // shift it back to the input element - if (_inputElementFocused && _inputHelper is not null) - { - _inputHelper.Focus(); - } - } - - [Parameter(CaptureUnmatchedValues = true)] - public IReadOnlyDictionary? AdditionalAttributes { get; set; } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - AvaloniaLocator.CurrentMutable.Bind().ToConstant((IJSInProcessRuntime)Js); - - _avaloniaModule = await AvaloniaModule.ImportAsync(Js); - - _canvasHelper = new FocusHelperInterop(_avaloniaModule, _htmlCanvas); - _containerHelper = new FocusHelperInterop(_avaloniaModule, _containerElement); - _inputHelper = new InputHelperInterop(_avaloniaModule, _inputElement); - - _inputHelper.CompositionEvent += InputHelperOnCompositionEvent; - _inputHelper.InputEvent += InputHelperOnInputEvent; - - HideIme(); - _canvasHelper.SetCursor("default"); - _topLevelImpl.SetCssCursor = x => - { - _inputHelper.SetCursor(x); //macOS - _canvasHelper.SetCursor(x); //windows - }; - - _nativeControlHost = new NativeControlHostInterop(_avaloniaModule, _nativeControlsContainer); - _storageProvider = await StorageProviderInterop.ImportAsync(Js); - - Console.WriteLine("starting html canvas setup"); - _interop = new SKHtmlCanvasInterop(_avaloniaModule, _htmlCanvas, OnRenderFrame); - - Console.WriteLine("Interop created"); - - var skiaOptions = AvaloniaLocator.Current.GetService(); - _useGL = skiaOptions?.CustomGpuFactory != null; - - if (_useGL) - { - _jsGlInfo = _interop.InitGL(); - Console.WriteLine("jsglinfo created - init gl"); - } - else - { - var rasterInitialized = _interop.InitRaster(); - Console.WriteLine("raster initialized: {0}", rasterInitialized); - } - - if (_useGL) - { - // create the SkiaSharp context - if (_context == null) - { - _glInterface = GRGlInterface.Create(); - _context = GRContext.CreateGl(_glInterface); - - - // bump the default resource cache limit - _context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024); - Console.WriteLine("glcontext created and resource limit set"); - } - - _topLevelImpl.SetSurface(_context, _jsGlInfo!, ColorType, - new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi); - } - else - { - _topLevelImpl.SetSurface(ColorType, - new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, _interop.PutImageData); - } - - _interop.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); - - Threading.Dispatcher.UIThread.Post(async () => - { - _interop.RequestAnimationFrame(true); - - _sizeWatcher = new SizeWatcherInterop(_avaloniaModule, _htmlCanvas, OnSizeChanged); - _dpiWatcher = new DpiWatcherInterop(_avaloniaModule, OnDpiChanged); - - _sizeWatcher.Start(); - _topLevel.Prepare(); - - _topLevel.Renderer.Start(); - }); - } - } - - private void InputHelperOnInputEvent(object? sender, WebInputEventArgs e) - { - if (IsComposing) - { - return; - } - - _topLevelImpl.RawTextEvent(e.Data); - - e.Handled = true; - } - - private void InputHelperOnCompositionEvent(object? sender, WebCompositionEventArgs e) - { - if(_client == null) - { - return; - } - - switch (e.Type) - { - case WebCompositionEventArgs.WebCompositionEventType.Start: - _client.SetPreeditText(null); - IsComposing = true; - break; - case WebCompositionEventArgs.WebCompositionEventType.Update: - _client.SetPreeditText(e.Data); - break; - case WebCompositionEventArgs.WebCompositionEventType.End: - IsComposing = false; - _client.SetPreeditText(null); - _topLevelImpl.RawTextEvent(e.Data); - break; - } - } - - private void OnRenderFrame() - { - if (_useGL && (_jsGlInfo == null)) - { - Console.WriteLine("nothing to render"); - return; - } - if (_canvasSize.Width <= 0 || _canvasSize.Height <= 0 || _dpi <= 0) - { - Console.WriteLine("nothing to render"); - return; - } - - ManualTriggerRenderTimer.Instance.RaiseTick(); - } - - public void Dispose() - { - _dpiWatcher?.Unsubscribe(OnDpiChanged); - _sizeWatcher?.Dispose(); - _interop?.Dispose(); - } - - private void ForceBlit() - { - // Note: this is technically a hack, but it's a kinda unique use case when - // we want to blit the previous frame - // renderer doesn't have much control over the render target - // we render on the UI thread - // We also don't want to have it as a meaningful public API. - // Therefore we have InternalsVisibleTo hack here. - - if (_topLevel.Renderer is CompositingRenderer dr) - { - dr.CompositionTarget.ImmediateUIThreadRender(); - } - } - - private void OnDpiChanged(double newDpi) - { - if (Math.Abs(_dpi - newDpi) > 0.0001) - { - _dpi = newDpi; - - _interop!.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); - - _topLevelImpl.SetClientSize(_canvasSize, _dpi); - - ForceBlit(); - } - } - - private void OnSizeChanged(SKSize newSize) - { - if (_canvasSize != newSize) - { - _canvasSize = newSize; - - _interop!.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); - - _topLevelImpl.SetClientSize(_canvasSize, _dpi); - - ForceBlit(); - } - } - - private void HideIme() - { - _inputHelper?.Hide(); - _containerHelper?.Focus(); - } - - public void SetClient(ITextInputMethodClient? client) - { - if (_inputHelper is null) - { - return; - } - - if(_client != null) - { - _client.SurroundingTextChanged -= SurroundingTextChanged; - } - - if(client != null) - { - client.SurroundingTextChanged += SurroundingTextChanged; - } - - _inputHelper.Clear(); - - _client = client; - - if (IsActive && _client != null) - { - _inputHelper.Show(); - _inputElementFocused = true; - _inputHelper.Focus(); - - var surroundingText = _client.SurroundingText; - - _inputHelper?.SetSurroundingText(surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset); - } - else - { - _inputElementFocused = false; - HideIme(); - } - } - - private void SurroundingTextChanged(object? sender, EventArgs e) - { - if(_client != null) - { - var surroundingText = _client.SurroundingText; - - _inputHelper?.SetSurroundingText(surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset); - } - } - - public void SetCursorRect(Rect rect) - { - _inputHelper?.Focus(); - var bounds = new PixelRect((int)rect.X, (int) rect.Y, (int) rect.Width, (int) rect.Height); - - _inputHelper?.SetBounds(bounds, _client?.SurroundingText.CursorOffset ?? 0); - _inputHelper?.Focus(); - } - - public void SetOptions(TextInputOptions options) - { - } - - public void Reset() - { - _inputHelper?.Clear(); - _inputHelper?.SetSurroundingText("", 0, 0); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs b/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs index 7970f09a58..26b4b15863 100644 --- a/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs +++ b/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs @@ -1,31 +1,28 @@ using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Media; -namespace Avalonia.Web.Blazor +namespace Avalonia.Web.Blazor; + +public static class WebAppBuilder { - public class BlazorSingleViewLifetime : ISingleViewApplicationLifetime + public static T SetupWithSingleViewLifetime( + this T builder) + where T : AppBuilderBase, new() { - public Control? MainView { get; set; } + return builder.SetupWithLifetime(new BlazorSingleViewLifetime()); } - public static class WebAppBuilder + public static AppBuilder Configure() + where TApp : Application, new() { - public static T SetupWithSingleViewLifetime( - this T builder) - where T : AppBuilderBase, new() - { - return builder.SetupWithLifetime(new BlazorSingleViewLifetime()); - } + var builder = AppBuilder.Configure() + .UseBrowser(); - public static AvaloniaBlazorAppBuilder Configure() - where TApp : Application, new() - { - var builder = AvaloniaBlazorAppBuilder.Configure() - .UseSkia() - .With(new SkiaOptions { CustomGpuFactory = () => new BlazorSkiaGpu() }); + return builder; + } - return builder; - } + internal class BlazorSingleViewLifetime : ISingleViewApplicationLifetime + { + public Control? MainView { get; set; } } } diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs deleted file mode 100644 index 6fa7bf0bde..0000000000 --- a/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Avalonia.Skia; - -namespace Avalonia.Web.Blazor -{ - public class BlazorSkiaGpu : ISkiaGpu - { - public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable surfaces) - { - foreach (var surface in surfaces) - { - if (surface is BlazorSkiaSurface blazorSkiaSurface) - { - return new BlazorSkiaGpuRenderTarget(blazorSkiaSurface); - } - } - - return null; - } - - public ISkiaSurface? TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session) - { - return null; - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderSession.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderSession.cs deleted file mode 100644 index 0c53825131..0000000000 --- a/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderSession.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Avalonia.Skia; -using SkiaSharp; - -namespace Avalonia.Web.Blazor -{ - internal class BlazorSkiaGpuRenderSession : ISkiaGpuRenderSession - { - private readonly SKSurface _surface; - - - public BlazorSkiaGpuRenderSession(BlazorSkiaSurface blazorSkiaSurface, GRBackendRenderTarget renderTarget) - { - _surface = SKSurface.Create(blazorSkiaSurface.Context, renderTarget, blazorSkiaSurface.Origin, blazorSkiaSurface.ColorType); - - GrContext = blazorSkiaSurface.Context; - - ScaleFactor = blazorSkiaSurface.Scaling; - - SurfaceOrigin = blazorSkiaSurface.Origin; - } - - public void Dispose() - { - _surface.Flush(); - - _surface.Dispose(); - } - - public GRContext GrContext { get; } - - public SKSurface SkSurface => _surface; - - public double ScaleFactor { get; } - - public GRSurfaceOrigin SurfaceOrigin { get; } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderTarget.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderTarget.cs deleted file mode 100644 index fa6a39f210..0000000000 --- a/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderTarget.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Avalonia.Skia; -using SkiaSharp; - -namespace Avalonia.Web.Blazor -{ - internal class BlazorSkiaGpuRenderTarget : ISkiaGpuRenderTarget - { - private readonly GRBackendRenderTarget _renderTarget; - private readonly BlazorSkiaSurface _blazorSkiaSurface; - private readonly PixelSize _size; - - public BlazorSkiaGpuRenderTarget(BlazorSkiaSurface blazorSkiaSurface) - { - _size = blazorSkiaSurface.Size; - - var glFbInfo = new GRGlFramebufferInfo(blazorSkiaSurface.GlInfo.FboId, blazorSkiaSurface.ColorType.ToGlSizedFormat()); - { - _blazorSkiaSurface = blazorSkiaSurface; - _renderTarget = new GRBackendRenderTarget( - (int)(blazorSkiaSurface.Size.Width * blazorSkiaSurface.Scaling), - (int)(blazorSkiaSurface.Size.Height * blazorSkiaSurface.Scaling), - blazorSkiaSurface.GlInfo.Samples, - blazorSkiaSurface.GlInfo.Stencils, glFbInfo); - } - } - - public void Dispose() - { - _renderTarget.Dispose(); - } - - public ISkiaGpuRenderSession BeginRenderingSession() - { - return new BlazorSkiaGpuRenderSession(_blazorSkiaSurface, _renderTarget); - } - - public bool IsCorrupted => _blazorSkiaSurface.Size != _size; - } -} diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs deleted file mode 100644 index 603a792de3..0000000000 --- a/src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Runtime.InteropServices; -using Avalonia.Controls.Platform.Surfaces; -using Avalonia.Platform; -using Avalonia.Skia; -using SkiaSharp; - -namespace Avalonia.Web.Blazor -{ - internal class BlazorSkiaRasterSurface : IBlazorSkiaSurface, IFramebufferPlatformSurface, IDisposable - { - public SKColorType ColorType { get; set; } - - public PixelSize Size { get; set; } - - public double Scaling { get; set; } - - private FramebufferData? _fbData; - private readonly Action _blitCallback; - private readonly Action _onDisposeAction; - - public BlazorSkiaRasterSurface( - SKColorType colorType, PixelSize size, double scaling, Action blitCallback) - { - ColorType = colorType; - Size = size; - Scaling = scaling; - _blitCallback = blitCallback; - _onDisposeAction = Blit; - } - - public void Dispose() - { - _fbData?.Dispose(); - _fbData = null; - } - - public ILockedFramebuffer Lock() - { - var bytesPerPixel = 4; // TODO: derive from ColorType - var dpi = Scaling * 96.0; - var width = (int)(Size.Width * Scaling); - var height = (int)(Size.Height * Scaling); - - if (_fbData is null || _fbData?.Size.Width != width || _fbData?.Size.Height != height) - { - _fbData?.Dispose(); - _fbData = new FramebufferData(width, height, bytesPerPixel); - } - - var pixelFormat = ColorType.ToPixelFormat(); - var data = _fbData.Value; - return new LockedFramebuffer( - data.Address, data.Size, data.RowBytes, - new Vector(dpi, dpi), pixelFormat, _onDisposeAction); - } - - private void Blit() - { - if (_fbData != null) - { - var data = _fbData.Value; - _blitCallback(data.Address, new SKSizeI(data.Size.Width, data.Size.Height)); - } - } - - private readonly struct FramebufferData - { - public PixelSize Size { get; } - - public int RowBytes { get; } - - public IntPtr Address { get; } - - public FramebufferData(int width, int height, int bytesPerPixel) - { - Size = new PixelSize(width, height); - RowBytes = width * bytesPerPixel; - Address = Marshal.AllocHGlobal(width * height * bytesPerPixel); - } - - public void Dispose() - { - Marshal.FreeHGlobal(Address); - } - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs deleted file mode 100644 index fb49df338b..0000000000 --- a/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Avalonia.Web.Blazor.Interop; -using SkiaSharp; - -namespace Avalonia.Web.Blazor -{ - internal class BlazorSkiaSurface : IBlazorSkiaSurface - { - public BlazorSkiaSurface(GRContext context, SKHtmlCanvasInterop.GLInfo glInfo, SKColorType colorType, PixelSize size, double scaling, GRSurfaceOrigin origin) - { - Context = context; - GlInfo = glInfo; - ColorType = colorType; - Size = size; - Scaling = scaling; - Origin = origin; - } - - public SKColorType ColorType { get; set; } - - public PixelSize Size { get; set; } - - public GRContext Context { get; set; } - - public GRSurfaceOrigin Origin { get; set; } - - public double Scaling { get; set; } - - public SKHtmlCanvasInterop.GLInfo GlInfo { get; set; } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/ClipboardImpl.cs b/src/Web/Avalonia.Web.Blazor/ClipboardImpl.cs deleted file mode 100644 index bafc07ca15..0000000000 --- a/src/Web/Avalonia.Web.Blazor/ClipboardImpl.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Input; -using Avalonia.Input.Platform; -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor -{ - internal class ClipboardImpl : IClipboard - { - public async Task GetTextAsync() - { - return await AvaloniaLocator.Current.GetRequiredService(). - InvokeAsync("navigator.clipboard.readText"); - } - - public async Task SetTextAsync(string text) - { - await AvaloniaLocator.Current.GetRequiredService(). - InvokeAsync("navigator.clipboard.writeText",text); - } - - public async Task ClearAsync() => await SetTextAsync(""); - - public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask; - - public Task GetFormatsAsync() => Task.FromResult(Array.Empty()); - - public Task GetDataAsync(string format) => Task.FromResult(new()); - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Cursor.cs b/src/Web/Avalonia.Web.Blazor/Cursor.cs deleted file mode 100644 index d921b2fa6c..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Cursor.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Avalonia.Input; -using Avalonia.Platform; - -namespace Avalonia.Web.Blazor -{ - public class CssCursor : ICursorImpl - { - public const string Default = "default"; - public string? Value { get; set; } - - public CssCursor(StandardCursorType type) - { - Value = ToKeyword(type); - } - - /// - /// Create a cursor from base64 image - /// - public CssCursor(string base64, string format, PixelPoint hotspot, StandardCursorType fallback) - { - Value = $"url(\"data:image/{format};base64,{base64}\") {hotspot.X} {hotspot.Y}, {ToKeyword(fallback)}"; - } - - /// - /// Create a cursor from url to *.cur file. - /// - public CssCursor(string url, StandardCursorType fallback) - { - Value = $"url('{url}'), {ToKeyword(fallback)}"; - } - - /// - /// Create a cursor from png/svg and hotspot position - /// - public CssCursor(string url, PixelPoint hotSpot, StandardCursorType fallback) - { - Value = $"url('{url}') {hotSpot.X} {hotSpot.Y}, {ToKeyword(fallback)}"; - } - - private static string ToKeyword(StandardCursorType type) => type switch - { - StandardCursorType.Hand => "pointer", - StandardCursorType.Cross => "crosshair", - StandardCursorType.Help => "help", - StandardCursorType.Ibeam => "text", - StandardCursorType.No => "not-allowed", - StandardCursorType.None => "none", - StandardCursorType.Wait => "progress", - StandardCursorType.AppStarting => "wait", - - StandardCursorType.DragMove => "move", - StandardCursorType.DragCopy => "copy", - StandardCursorType.DragLink => "alias", - - StandardCursorType.UpArrow => "default",/*not found matching one*/ - StandardCursorType.SizeWestEast => "ew-resize", - StandardCursorType.SizeNorthSouth => "ns-resize", - StandardCursorType.SizeAll => "move", - - StandardCursorType.TopSide => "n-resize", - StandardCursorType.BottomSide => "s-resize", - StandardCursorType.LeftSide => "w-resize", - StandardCursorType.RightSide => "e-resize", - StandardCursorType.TopLeftCorner => "nw-resize", - StandardCursorType.TopRightCorner => "ne-resize", - StandardCursorType.BottomLeftCorner => "sw-resize", - StandardCursorType.BottomRightCorner => "se-resize", - - _ => Default, - }; - - public void Dispose() {} - } - - internal class CssCursorFactory : ICursorFactory - { - public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) - { - using var imageStream = new MemoryStream(); - cursor.Save(imageStream); - - //not memory optimized because CryptoStream with ToBase64Transform is not supported in the browser. - var base64String = Convert.ToBase64String(imageStream.ToArray()); - return new CssCursor(base64String, "png", hotSpot, StandardCursorType.Arrow); - } - - ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType) - { - return new CssCursor(cursorType); - } - } -} - diff --git a/src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs b/src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs deleted file mode 100644 index 5463893e27..0000000000 --- a/src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Avalonia.Web.Blazor -{ - internal interface IBlazorSkiaSurface - { - public PixelSize Size { get; set; } - - public double Scaling { get; set; } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/ActionHelper.cs b/src/Web/Avalonia.Web.Blazor/Interop/ActionHelper.cs deleted file mode 100644 index 8bb266a942..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/ActionHelper.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.ComponentModel; -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop -{ - [EditorBrowsable(EditorBrowsableState.Never)] - public class ActionHelper - { - private readonly Action action; - - public ActionHelper(Action action) - { - this.action = action; - } - - [JSInvokable] - public void Invoke() => action?.Invoke(); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public class ActionHelper - { - private readonly Action action; - - public ActionHelper(Action action) - { - this.action = action; - } - - [JSInvokable] - public void Invoke(T param1) => action?.Invoke(param1); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public class ActionHelper - { - private readonly Action action; - - public ActionHelper(Action action) - { - this.action = action; - } - - [JSInvokable] - public void Invoke(T1 p1, T2 p2) => action?.Invoke(p1, p2); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public class ActionHelper - { - private readonly Action action; - - public ActionHelper(Action action) - { - this.action = action; - } - - [JSInvokable] - public void Invoke(T1 p1, T2 p2, T3 p3) => action?.Invoke(p1, p2, p3); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public class ActionHelper - { - private readonly Action action; - - public ActionHelper(Action action) - { - this.action = action; - } - - [JSInvokable] - public void Invoke(T1 p1, T2 p2, T3 p3, T4 p4) => action?.Invoke(p1, p2, p3, p4); - } - - - - - -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/AvaloniaModule.cs b/src/Web/Avalonia.Web.Blazor/Interop/AvaloniaModule.cs deleted file mode 100644 index ff13e95aa7..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/AvaloniaModule.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop -{ - internal class AvaloniaModule : JSModuleInterop - { - private AvaloniaModule(IJSRuntime js) : base(js, "./_content/Avalonia.Web.Blazor/Avalonia.js") - { - } - - public static async Task ImportAsync(IJSRuntime js) - { - var interop = new AvaloniaModule(js); - await interop.ImportAsync(); - return interop; - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs deleted file mode 100644 index c86c72f29c..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop -{ - internal class DpiWatcherInterop : IDisposable - { - private const string StartSymbol = "DpiWatcher.start"; - private const string StopSymbol = "DpiWatcher.stop"; - private const string GetDpiSymbol = "DpiWatcher.getDpi"; - - private event Action? callbacksEvent; - private readonly ActionHelper _callbackHelper; - private readonly AvaloniaModule _module; - - private DotNetObjectReference>? callbackReference; - - public DpiWatcherInterop(AvaloniaModule module, Action? callback = null) - { - _module = module; - _callbackHelper = new ActionHelper((o, n) => callbacksEvent?.Invoke(n)); - - if (callback != null) - Subscribe(callback); - } - - public void Dispose() => Stop(); - - public void Subscribe(Action callback) - { - var shouldStart = callbacksEvent == null; - - callbacksEvent += callback; - - var dpi = shouldStart - ? Start() - : GetDpi(); - - callback(dpi); - } - - public void Unsubscribe(Action callback) - { - callbacksEvent -= callback; - - if (callbacksEvent == null) - Stop(); - } - - private double Start() - { - if (callbackReference != null) - return GetDpi(); - - callbackReference = DotNetObjectReference.Create(_callbackHelper); - - return _module.Invoke(StartSymbol, callbackReference); - } - - private void Stop() - { - if (callbackReference == null) - return; - - _module.Invoke(StopSymbol); - - callbackReference?.Dispose(); - callbackReference = null; - } - - public double GetDpi() => _module.Invoke(GetDpiSymbol); - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/FocusHelperInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/FocusHelperInterop.cs deleted file mode 100644 index 090909f98f..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/FocusHelperInterop.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.AspNetCore.Components; - -namespace Avalonia.Web.Blazor.Interop; - -internal class FocusHelperInterop -{ - private const string FocusSymbol = "FocusHelper.focus"; - private const string SetCursorSymbol = "FocusHelper.setCursor"; - - private readonly AvaloniaModule _module; - private readonly ElementReference _inputElement; - - public FocusHelperInterop(AvaloniaModule module, ElementReference inputElement) - { - _module = module; - _inputElement = inputElement; - } - - public void Focus() => _module.Invoke(FocusSymbol, _inputElement); - - public void SetCursor(string kind) => _module.Invoke(SetCursorSymbol, _inputElement, kind); -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs deleted file mode 100644 index 8872339f91..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs +++ /dev/null @@ -1,130 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop -{ - internal class WebCompositionEventArgs : EventArgs - { - public enum WebCompositionEventType - { - Start, - Update, - End - } - - public WebCompositionEventArgs(string type, string data) - { - Type = type switch - { - "compositionstart" => WebCompositionEventType.Start, - "compositionupdate" => WebCompositionEventType.Update, - "compositionend" => WebCompositionEventType.End, - _ => Type - }; - - Data = data; - } - - public WebCompositionEventType Type { get; } - - public string Data { get; } - } - - internal class WebInputEventArgs - { - public WebInputEventArgs(string type, string data) - { - Type = type; - Data = data; - } - - public string Type { get; } - - public string Data { get; } - - public bool Handled { get; set; } - } - - internal class InputHelperInterop - { - private const string ClearSymbol = "InputHelper.clear"; - private const string FocusSymbol = "InputHelper.focus"; - private const string SetCursorSymbol = "InputHelper.setCursor"; - private const string HideSymbol = "InputHelper.hide"; - private const string ShowSymbol = "InputHelper.show"; - private const string StartSymbol = "InputHelper.start"; - private const string SetSurroundingTextSymbol = "InputHelper.setSurroundingText"; - private const string SetBoundsSymbol = "InputHelper.setBounds"; - - private readonly AvaloniaModule _module; - private readonly ElementReference _inputElement; - private readonly ActionHelper _compositionAction; - private readonly ActionHelper _inputAction; - - private DotNetObjectReference>? compositionActionReference; - private DotNetObjectReference>? inputActionReference; - - public InputHelperInterop(AvaloniaModule module, ElementReference inputElement) - { - _module = module; - _inputElement = inputElement; - - _compositionAction = new ActionHelper(OnCompositionEvent); - _inputAction = new ActionHelper(OnInputEvent); - - Start(); - } - - public event EventHandler? CompositionEvent; - public event EventHandler? InputEvent; - - private void OnCompositionEvent(string type, string data) - { - Console.WriteLine($"CompositionEvent Handler Helper {CompositionEvent == null} "); - CompositionEvent?.Invoke(this, new WebCompositionEventArgs(type, data)); - } - - private void OnInputEvent(string type, string data) - { - Console.WriteLine($"InputEvent Handler Helper {InputEvent == null} "); - - var args = new WebInputEventArgs(type, data); - - InputEvent?.Invoke(this, args); - } - - public void Clear() => _module.Invoke(ClearSymbol, _inputElement); - - public void Focus() => _module.Invoke(FocusSymbol, _inputElement); - - public void SetCursor(string kind) => _module.Invoke(SetCursorSymbol, _inputElement, kind); - - public void Hide() => _module.Invoke(HideSymbol, _inputElement); - - public void Show() => _module.Invoke(ShowSymbol, _inputElement); - - private void Start() - { - if(compositionActionReference != null) - { - return; - } - - compositionActionReference = DotNetObjectReference.Create(_compositionAction); - - inputActionReference = DotNetObjectReference.Create(_inputAction); - - _module.Invoke(StartSymbol, _inputElement, compositionActionReference, inputActionReference); - } - - public void SetSurroundingText(string text, int start, int end) - { - _module.Invoke(SetSurroundingTextSymbol, _inputElement, text, start, end); - } - - public void SetBounds(PixelRect bounds, int caret) - { - _module.Invoke(SetBoundsSymbol, _inputElement, bounds.X, bounds.Y, bounds.Width, bounds.Height, caret); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs deleted file mode 100644 index dca1b53650..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop -{ - internal class JSModuleInterop : IDisposable - { - private readonly Task moduleTask; - private IJSUnmarshalledObjectReference? module; - - public JSModuleInterop(IJSRuntime js, string filename) - { - if (js is not IJSInProcessRuntime) - throw new NotSupportedException("SkiaSharp currently only works on Web Assembly."); - - moduleTask = js.InvokeAsync("import", filename).AsTask(); - } - - public async Task ImportAsync() - { - module = await moduleTask; - } - - public void Dispose() - { - OnDisposingModule(); - Module.Dispose(); - } - - protected IJSUnmarshalledObjectReference Module => - module ?? throw new InvalidOperationException("Make sure to run ImportAsync() first."); - - internal void Invoke(string identifier, params object?[]? args) => - Module.InvokeVoid(identifier, args); - - internal TValue Invoke(string identifier, params object?[]? args) => - Module.Invoke(identifier, args); - - internal ValueTask InvokeAsync(string identifier, params object?[]? args) => - Module.InvokeVoidAsync(identifier, args); - - internal ValueTask InvokeAsync(string identifier, params object?[]? args) => - Module.InvokeAsync(identifier, args); - - protected virtual void OnDisposingModule() { } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/NativeControlHostImpl.cs b/src/Web/Avalonia.Web.Blazor/Interop/NativeControlHostImpl.cs deleted file mode 100644 index b824fcae46..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/NativeControlHostImpl.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -using Avalonia.Controls.Platform; -using Avalonia.Platform; - -using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop -{ - - internal class NativeControlHostInterop : INativeControlHostImpl - { - private const string CreateDefaultChildSymbol = "NativeControlHost.CreateDefaultChild"; - private const string CreateAttachmentSymbol = "NativeControlHost.CreateAttachment"; - private const string GetReferenceSymbol = "NativeControlHost.GetReference"; - - private readonly AvaloniaModule _module; - private readonly ElementReference _hostElement; - - public NativeControlHostInterop(AvaloniaModule module, ElementReference element) - { - _module = module; - _hostElement = element; - } - - public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent) - { - var element = _module.Invoke(CreateDefaultChildSymbol); - return new JSObjectControlHandle(element); - } - - public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func create) - { - Attachment? a = null; - try - { - using var hostElementJsReference = _module.Invoke(GetReferenceSymbol, _hostElement); - var child = create(new JSObjectControlHandle(hostElementJsReference)); - var attachmenetReference = _module.Invoke(CreateAttachmentSymbol); - // It has to be assigned to the variable before property setter is called so we dispose it on exception -#pragma warning disable IDE0017 // Simplify object initialization - a = new Attachment(attachmenetReference, child); -#pragma warning restore IDE0017 // Simplify object initialization - a.AttachedTo = this; - return a; - } - catch - { - a?.Dispose(); - throw; - } - } - - public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle) - { - var attachmenetReference = _module.Invoke(CreateAttachmentSymbol); - var a = new Attachment(attachmenetReference, handle); - a.AttachedTo = this; - return a; - } - - public bool IsCompatibleWith(IPlatformHandle handle) => handle is JSObjectControlHandle; - - class Attachment : INativeControlHostControlTopLevelAttachment - { - private const string InitializeWithChildHandleSymbol = "InitializeWithChildHandle"; - private const string AttachToSymbol = "AttachTo"; - private const string ShowInBoundsSymbol = "ShowInBounds"; - private const string HideWithSizeSymbol = "HideWithSize"; - private const string ReleaseChildSymbol = "ReleaseChild"; - - private IJSInProcessObjectReference? _native; - private NativeControlHostInterop? _attachedTo; - - public Attachment(IJSInProcessObjectReference native, IPlatformHandle handle) - { - _native = native; - _native.InvokeVoid(InitializeWithChildHandleSymbol, ((JSObjectControlHandle)handle).Object); - } - - public void Dispose() - { - if (_native != null) - { - _native.InvokeVoid(ReleaseChildSymbol); - _native.Dispose(); - _native = null; - } - } - - public INativeControlHostImpl? AttachedTo - { - get => _attachedTo!; - set - { - CheckDisposed(); - - var host = (NativeControlHostInterop?)value; - if (host == null) - { - _native.InvokeVoid(AttachToSymbol); - } - else - { - _native.InvokeVoid(AttachToSymbol, host._hostElement); - } - _attachedTo = host; - } - } - - public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostInterop; - - public void HideWithSize(Size size) - { - CheckDisposed(); - if (_attachedTo == null) - return; - - _native.InvokeVoid(HideWithSizeSymbol, Math.Max(1, (float)size.Width), Math.Max(1, (float)size.Height)); - } - - public void ShowInBounds(Rect bounds) - { - CheckDisposed(); - - if (_attachedTo == null) - throw new InvalidOperationException("Native control isn't attached to a toplevel"); - - bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width), - Math.Max(1, bounds.Height)); - - _native.InvokeVoid(ShowInBoundsSymbol, (float)bounds.X, (float)bounds.Y, (float)bounds.Width, (float)bounds.Height); - } - - [MemberNotNull(nameof(_native))] - private void CheckDisposed() - { - if (_native == null) - throw new ObjectDisposedException(nameof(Attachment)); - } - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs deleted file mode 100644 index cf9350fb62..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; -using SkiaSharp; - -namespace Avalonia.Web.Blazor.Interop -{ - internal class SKHtmlCanvasInterop : IDisposable - { - private const string JsFilename = "./_content/Avalonia.Web.Blazor/SKHtmlCanvas.js"; - private const string InitGLSymbol = "SKHtmlCanvas.initGL"; - private const string InitRasterSymbol = "SKHtmlCanvas.initRaster"; - private const string DeinitSymbol = "SKHtmlCanvas.deinit"; - private const string RequestAnimationFrameSymbol = "SKHtmlCanvas.requestAnimationFrame"; - private const string SetCanvasSizeSymbol = "SKHtmlCanvas.setCanvasSize"; - private const string PutImageDataSymbol = "SKHtmlCanvas.putImageData"; - - private readonly AvaloniaModule _module; - private readonly ElementReference _htmlCanvas; - private readonly string _htmlElementId; - private readonly ActionHelper _callbackHelper; - - private DotNetObjectReference? callbackReference; - - public SKHtmlCanvasInterop(AvaloniaModule module, ElementReference element, Action renderFrameCallback) - { - _module = module; - _htmlCanvas = element; - _htmlElementId = element.Id; - - _callbackHelper = new ActionHelper(renderFrameCallback); - } - - public void Dispose() => Deinit(); - - public GLInfo InitGL() - { - if (callbackReference != null) - throw new InvalidOperationException("Unable to initialize the same canvas more than once."); - - callbackReference = DotNetObjectReference.Create(_callbackHelper); - - return _module.Invoke(InitGLSymbol, _htmlCanvas, _htmlElementId, callbackReference); - } - - public bool InitRaster() - { - if (callbackReference != null) - throw new InvalidOperationException("Unable to initialize the same canvas more than once."); - - callbackReference = DotNetObjectReference.Create(_callbackHelper); - - return _module.Invoke(InitRasterSymbol, _htmlCanvas, _htmlElementId, callbackReference); - } - - public void Deinit() - { - if (callbackReference == null) - return; - - _module.Invoke(DeinitSymbol, _htmlElementId); - - callbackReference?.Dispose(); - } - - public void RequestAnimationFrame(bool enableRenderLoop) => - _module.Invoke(RequestAnimationFrameSymbol, _htmlCanvas, enableRenderLoop); - - public void SetCanvasSize(int rawWidth, int rawHeight) => - _module.Invoke(SetCanvasSizeSymbol, _htmlCanvas, rawWidth, rawHeight); - - public void PutImageData(IntPtr intPtr, SKSizeI rawSize) => - _module.Invoke(PutImageDataSymbol, _htmlCanvas, intPtr.ToInt64(), rawSize.Width, rawSize.Height); - - public record GLInfo(int ContextId, uint FboId, int Stencils, int Samples, int Depth); - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs deleted file mode 100644 index e21ae837d0..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; -using SkiaSharp; - -namespace Avalonia.Web.Blazor.Interop -{ - internal class SizeWatcherInterop : IDisposable - { - private const string ObserveSymbol = "SizeWatcher.observe"; - private const string UnobserveSymbol = "SizeWatcher.unobserve"; - - private readonly AvaloniaModule _module; - private readonly ElementReference _htmlElement; - private readonly string _htmlElementId; - private readonly ActionHelper _callbackHelper; - - private DotNetObjectReference>? callbackReference; - - public SizeWatcherInterop(AvaloniaModule module, ElementReference element, Action callback) - { - _module = module; - _htmlElement = element; - _htmlElementId = element.Id; - _callbackHelper = new ActionHelper((x, y) => callback(new SKSize(x, y))); - } - - public void Dispose() => Stop(); - - public void Start() - { - if (callbackReference != null) - return; - - callbackReference = DotNetObjectReference.Create(_callbackHelper); - - _module.Invoke(ObserveSymbol, _htmlElement, _htmlElementId, callbackReference); - } - - public void Stop() - { - if (callbackReference == null) - return; - - _module.Invoke(UnobserveSymbol, _htmlElementId); - - callbackReference?.Dispose(); - callbackReference = null; - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs deleted file mode 100644 index 690d9683df..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -using Avalonia.Platform.Storage; - -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop.Storage -{ - internal record FilePickerAcceptType(string Description, IReadOnlyDictionary> Accept); - - internal record FileProperties(ulong Size, long LastModified, string? Type); - - internal class StorageProviderInterop : JSModuleInterop, IStorageProvider - { - private const string JsFilename = "./_content/Avalonia.Web.Blazor/Storage.js"; - private const string PickerCancelMessage = "The user aborted a request"; - - public static async Task ImportAsync(IJSRuntime js) - { - var interop = new StorageProviderInterop(js); - await interop.ImportAsync(); - return interop; - } - - public StorageProviderInterop(IJSRuntime js) - : base(js, JsFilename) - { - } - - public bool CanOpen => Invoke("StorageProvider.canOpen"); - public bool CanSave => Invoke("StorageProvider.canSave"); - public bool CanPickFolder => Invoke("StorageProvider.canPickFolder"); - - public async Task> OpenFilePickerAsync(FilePickerOpenOptions options) - { - try - { - var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; - - var (types, exludeAll) = ConvertFileTypes(options.FileTypeFilter); - var items = await InvokeAsync("StorageProvider.openFileDialog", startIn, options.AllowMultiple, types, exludeAll); - var count = items.Invoke("count"); - - return Enumerable.Range(0, count) - .Select(index => new JSStorageFile(items.Invoke("at", index))) - .ToArray(); - } - catch (JSException ex) when (ex.Message.Contains(PickerCancelMessage, StringComparison.Ordinal)) - { - return Array.Empty(); - } - } - - public async Task SaveFilePickerAsync(FilePickerSaveOptions options) - { - try - { - var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; - - var (types, exludeAll) = ConvertFileTypes(options.FileTypeChoices); - var item = await InvokeAsync("StorageProvider.saveFileDialog", startIn, options.SuggestedFileName, types, exludeAll); - - return item is not null ? new JSStorageFile(item) : null; - } - catch (JSException ex) when (ex.Message.Contains(PickerCancelMessage, StringComparison.Ordinal)) - { - return null; - } - } - - public async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) - { - try - { - var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; - - var item = await InvokeAsync("StorageProvider.selectFolderDialog", startIn); - - return item is not null ? new[] { new JSStorageFolder(item) } : Array.Empty(); - } - catch (JSException ex) when (ex.Message.Contains(PickerCancelMessage, StringComparison.Ordinal)) - { - return Array.Empty(); - } - } - - public async Task OpenFileBookmarkAsync(string bookmark) - { - var item = await InvokeAsync("StorageProvider.openBookmark", bookmark); - return item is not null ? new JSStorageFile(item) : null; - } - - public async Task OpenFolderBookmarkAsync(string bookmark) - { - var item = await InvokeAsync("StorageProvider.openBookmark", bookmark); - return item is not null ? new JSStorageFolder(item) : null; - } - - private static (FilePickerAcceptType[]? types, bool excludeAllOption) ConvertFileTypes(IEnumerable? input) - { - var types = input? - .Where(t => t.MimeTypes?.Any() == true && t != FilePickerFileTypes.All) - .Select(t => new FilePickerAcceptType(t.Name, t.MimeTypes! - .ToDictionary(m => m, _ => (IReadOnlyList)Array.Empty()))) - .ToArray(); - if (types?.Length == 0) - { - types = null; - } - - var inlcudeAll = input?.Contains(FilePickerFileTypes.All) == true || types is null; - - return (types, !inlcudeAll); - } - } - - internal abstract class JSStorageItem : IStorageBookmarkItem - { - internal IJSInProcessObjectReference? _fileHandle; - - protected JSStorageItem(IJSInProcessObjectReference fileHandle) - { - _fileHandle = fileHandle ?? throw new ArgumentNullException(nameof(fileHandle)); - } - - internal IJSInProcessObjectReference FileHandle => _fileHandle ?? throw new ObjectDisposedException(nameof(JSStorageItem)); - - public string Name => FileHandle.Invoke("getName"); - - public bool TryGetUri([NotNullWhen(true)] out Uri? uri) - { - uri = new Uri(Name, UriKind.Relative); - return false; - } - - public async Task GetBasicPropertiesAsync() - { - var properties = await FileHandle.InvokeAsync("getProperties"); - - return new StorageItemProperties( - properties?.Size, - dateCreated: null, - dateModified: properties?.LastModified > 0 ? DateTimeOffset.FromUnixTimeMilliseconds(properties.LastModified) : null); - } - - public bool CanBookmark => true; - - public Task SaveBookmarkAsync() - { - return FileHandle.InvokeAsync("saveBookmark").AsTask(); - } - - public Task GetParentAsync() - { - return Task.FromResult(null); - } - - public Task ReleaseBookmarkAsync() - { - return FileHandle.InvokeAsync("deleteBookmark").AsTask(); - } - - public void Dispose() - { - _fileHandle?.Dispose(); - _fileHandle = null; - } - } - - internal class JSStorageFile : JSStorageItem, IStorageBookmarkFile - { - public JSStorageFile(IJSInProcessObjectReference fileHandle) : base(fileHandle) - { - } - - public bool CanOpenRead => true; - public async Task OpenReadAsync() - { - var stream = await FileHandle.InvokeAsync("openRead"); - // Remove maxAllowedSize limit, as developer can decide if they read only small part or everything. - return await stream.OpenReadStreamAsync(long.MaxValue, CancellationToken.None); - } - - public bool CanOpenWrite => true; - public async Task OpenWriteAsync() - { - var properties = await FileHandle.InvokeAsync("getProperties"); - var streamWriter = await FileHandle.InvokeAsync("openWrite"); - - return new JSWriteableStream(streamWriter, (long)(properties?.Size ?? 0)); - } - } - - internal class JSStorageFolder : JSStorageItem, IStorageBookmarkFolder - { - public JSStorageFolder(IJSInProcessObjectReference fileHandle) : base(fileHandle) - { - } - - public async Task> GetItemsAsync() - { - var items = await FileHandle.InvokeAsync("getItems"); - if (items is null) - { - return Array.Empty(); - } - - var count = items.Invoke("count"); - - return Enumerable.Range(0, count) - .Select(index => - { - var reference = items.Invoke("at", index); - return reference.Invoke("getKind") switch - { - "directory" => (IStorageItem)new JSStorageFolder(reference), - "file" => new JSStorageFile(reference), - _ => null - }; - }) - .Where(i => i is not null) - .ToArray()!; - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/Storage/WriteableStream.cs b/src/Web/Avalonia.Web.Blazor/Interop/Storage/WriteableStream.cs deleted file mode 100644 index 55a7831b1a..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Interop/Storage/WriteableStream.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System.Buffers; -using System.Text.Json.Serialization; - -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor.Interop.Storage -{ - // Loose wrapper implementaion of a stream on top of FileAPI FileSystemWritableFileStream - internal sealed class JSWriteableStream : Stream - { - private IJSInProcessObjectReference? _jSReference; - - // Unfortunatelly we can't read current length/position, so we need to keep it C#-side only. - private long _length, _position; - - internal JSWriteableStream(IJSInProcessObjectReference jSReference, long initialLength) - { - _jSReference = jSReference; - _length = initialLength; - } - - private IJSInProcessObjectReference JSReference => _jSReference ?? throw new ObjectDisposedException(nameof(JSWriteableStream)); - - public override bool CanRead => false; - - public override bool CanSeek => true; - - public override bool CanWrite => true; - - public override long Length => _length; - - public override long Position - { - get => _position; - set => Seek(_position, SeekOrigin.Begin); - } - - public override void Flush() - { - // no-op - } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - var position = origin switch - { - SeekOrigin.Current => _position + offset, - SeekOrigin.End => _length + offset, - _ => offset - }; - JSReference.InvokeVoid("seek", position); - return position; - } - - public override void SetLength(long value) - { - _length = value; - - // See https://docs.w3cub.com/dom/filesystemwritablefilestream/truncate - // If the offset is smaller than the size, it remains unchanged. If the offset is larger than size, the offset is set to that size - if (_position > _length) - { - _position = _length; - } - - JSReference.InvokeVoid("truncate", value); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException("Synchronous writes are not supported."); - } - - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - if (offset != 0 || count != buffer.Length) - { - // TODO, we need to pass prepared buffer to the JS - // Can't use ArrayPool as it can return bigger array than requested - // Can't use Span/Memory, as it's not supported by JS interop yet. - // Alternatively we can pass original buffer and offset+count, so it can be trimmed on the JS side (but is it more efficient tho?) - buffer = buffer.AsMemory(offset, count).ToArray(); - } - return WriteAsyncInternal(buffer, cancellationToken).AsTask(); - } - - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - { - return WriteAsyncInternal(buffer.ToArray(), cancellationToken); - } - - private ValueTask WriteAsyncInternal(byte[] buffer, CancellationToken _) - { - _position += buffer.Length; - - return JSReference.InvokeVoidAsync("write", buffer); - } - - protected override void Dispose(bool disposing) - { - if (_jSReference is { } jsReference) - { - _jSReference = null; - jsReference.InvokeVoid("close"); - jsReference.Dispose(); - } - } - - public override async ValueTask DisposeAsync() - { - if (_jSReference is { } jsReference) - { - _jSReference = null; - await jsReference.InvokeVoidAsync("close"); - await jsReference.DisposeAsync(); - } - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/JSObjectControlHandle.cs b/src/Web/Avalonia.Web.Blazor/JSObjectControlHandle.cs deleted file mode 100644 index 4426c3fbd7..0000000000 --- a/src/Web/Avalonia.Web.Blazor/JSObjectControlHandle.cs +++ /dev/null @@ -1,35 +0,0 @@ -#nullable enable -using Avalonia.Controls.Platform; - -using Microsoft.JSInterop; - -namespace Avalonia.Web.Blazor -{ - public class JSObjectControlHandle : INativeControlHostDestroyableControlHandle - { - internal const string ElementReferenceDescriptor = "JSObjectReference"; - - public JSObjectControlHandle(IJSObjectReference reference) - { - Object = reference; - } - - public IJSObjectReference Object { get; } - - public IntPtr Handle => throw new NotSupportedException(); - - public string? HandleDescriptor => ElementReferenceDescriptor; - - public void Destroy() - { - if (Object is IJSInProcessObjectReference inProcess) - { - inProcess.Dispose(); - } - else - { - _ = Object.DisposeAsync(); - } - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/Keycodes.cs b/src/Web/Avalonia.Web.Blazor/Keycodes.cs deleted file mode 100644 index ea30f0a9f0..0000000000 --- a/src/Web/Avalonia.Web.Blazor/Keycodes.cs +++ /dev/null @@ -1,127 +0,0 @@ -using Avalonia.Input; - -namespace Avalonia.Web.Blazor -{ - internal static class Keycodes - { - public static Dictionary KeyCodes = new() - { - { "Escape", Key.Escape }, - { "Digit1", Key.D1 }, - { "Digit2", Key.D2 }, - { "Digit3", Key.D3 }, - { "Digit4", Key.D4 }, - { "Digit5", Key.D5 }, - { "Digit6", Key.D6 }, - { "Digit7", Key.D7 }, - { "Digit8", Key.D8 }, - { "Digit9", Key.D9 }, - { "Digit0", Key.D0 }, - { "Minus", Key.OemMinus }, - //{ "Equal" , Key. }, - { "Backspace", Key.Back }, - { "Tab", Key.Tab }, - { "KeyQ", Key.Q }, - { "KeyW", Key.W }, - { "KeyE", Key.E }, - { "KeyR", Key.R }, - { "KeyT", Key.T }, - { "KeyY", Key.Y }, - { "KeyU", Key.U }, - { "KeyI", Key.I }, - { "KeyO", Key.O }, - { "KeyP", Key.P }, - { "BracketLeft", Key.OemOpenBrackets }, - { "BracketRight", Key.OemCloseBrackets }, - { "Enter", Key.Enter }, - { "ControlLeft", Key.LeftCtrl }, - { "KeyA", Key.A }, - { "KeyS", Key.S }, - { "KeyD", Key.D }, - { "KeyF", Key.F }, - { "KeyG", Key.G }, - { "KeyH", Key.H }, - { "KeyJ", Key.J }, - { "KeyK", Key.K }, - { "KeyL", Key.L }, - { "Semicolon", Key.OemSemicolon }, - { "Quote", Key.OemQuotes }, - //{ "Backquote" , Key. }, - { "ShiftLeft", Key.LeftShift }, - { "Backslash", Key.OemBackslash }, - { "KeyZ", Key.Z }, - { "KeyX", Key.X }, - { "KeyC", Key.C }, - { "KeyV", Key.V }, - { "KeyB", Key.B }, - { "KeyN", Key.N }, - { "KeyM", Key.M }, - { "Comma", Key.OemComma }, - { "Period", Key.OemPeriod }, - //{ "Slash" , Key. }, - { "ShiftRight", Key.RightShift }, - { "NumpadMultiply", Key.Multiply }, - { "AltLeft", Key.LeftAlt }, - { "Space", Key.Space }, - { "CapsLock", Key.CapsLock }, - { "F1", Key.F1 }, - { "F2", Key.F2 }, - { "F3", Key.F3 }, - { "F4", Key.F4 }, - { "F5", Key.F5 }, - { "F6", Key.F6 }, - { "F7", Key.F7 }, - { "F8", Key.F8 }, - { "F9", Key.F9 }, - { "F10", Key.F10 }, - { "NumLock", Key.NumLock }, - { "ScrollLock", Key.Scroll }, - { "Numpad7", Key.NumPad7 }, - { "Numpad8", Key.NumPad8 }, - { "Numpad9", Key.NumPad9 }, - { "NumpadSubtract", Key.Subtract }, - { "Numpad4", Key.NumPad4 }, - { "Numpad5", Key.NumPad5 }, - { "Numpad6", Key.NumPad6 }, - { "NumpadAdd", Key.Add }, - { "Numpad1", Key.NumPad1 }, - { "Numpad2", Key.NumPad2 }, - { "Numpad3", Key.NumPad3 }, - { "Numpad0", Key.NumPad0 }, - { "NumpadDecimal", Key.Decimal }, - { "Unidentified", Key.NoName }, - //{ "IntlBackslash" , Key.bac }, - { "F11", Key.F11 }, - { "F12", Key.F12 }, - //{ "IntlRo" , Key.Ro }, - //{ "Unidentified" , Key. }, - { "Convert", Key.ImeConvert }, - { "KanaMode", Key.KanaMode }, - { "NonConvert", Key.ImeNonConvert }, - //{ "Unidentified" , Key. }, - { "NumpadEnter", Key.Enter }, - { "ControlRight", Key.RightCtrl }, - { "NumpadDivide", Key.Divide }, - { "PrintScreen", Key.PrintScreen }, - { "AltRight", Key.RightAlt }, - //{ "Unidentified" , Key. }, - { "Home", Key.Home }, - { "ArrowUp", Key.Up }, - { "PageUp", Key.PageUp }, - { "ArrowLeft", Key.Left }, - { "ArrowRight", Key.Right }, - { "End", Key.End }, - { "ArrowDown", Key.Down }, - { "PageDown", Key.PageDown }, - { "Insert", Key.Insert }, - { "Delete", Key.Delete }, - //{ "Unidentified" , Key. }, - { "AudioVolumeMute", Key.VolumeMute }, - { "AudioVolumeDown", Key.VolumeDown }, - { "AudioVolumeUp", Key.VolumeUp }, - //{ "NumpadEqual" , Key. }, - { "Pause", Key.Pause }, - { "NumpadComma", Key.OemComma } - }; - } -} diff --git a/src/Web/Avalonia.Web.Blazor/ManualTriggerRenderTimer.cs b/src/Web/Avalonia.Web.Blazor/ManualTriggerRenderTimer.cs deleted file mode 100644 index 7b9feab2e3..0000000000 --- a/src/Web/Avalonia.Web.Blazor/ManualTriggerRenderTimer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Diagnostics; -using Avalonia.Rendering; - -namespace Avalonia.Web.Blazor -{ - public class ManualTriggerRenderTimer : IRenderTimer - { - private static readonly Stopwatch s_sw = Stopwatch.StartNew(); - - public static ManualTriggerRenderTimer Instance { get; } = new(); - - public void RaiseTick() => Tick?.Invoke(s_sw.Elapsed); - - public event Action? Tick; - public bool RunsInBackground => false; - } -} diff --git a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs deleted file mode 100644 index 3a09c16932..0000000000 --- a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs +++ /dev/null @@ -1,222 +0,0 @@ -using System.Diagnostics; -using Avalonia.Controls; -using Avalonia.Controls.Platform; -using Avalonia.Input; -using Avalonia.Input.Raw; -using Avalonia.Input.TextInput; -using Avalonia.Platform; -using Avalonia.Platform.Storage; -using Avalonia.Rendering; -using Avalonia.Rendering.Composition; -using Avalonia.Web.Blazor.Interop; -using SkiaSharp; - -#nullable enable - -namespace Avalonia.Web.Blazor -{ - internal class RazorViewTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider - { - private Size _clientSize; - private IBlazorSkiaSurface? _currentSurface; - private IInputRoot? _inputRoot; - private readonly Stopwatch _sw = Stopwatch.StartNew(); - private readonly AvaloniaView _avaloniaView; - private readonly TouchDevice _touchDevice; - private readonly PenDevice _penDevice; - private string _currentCursor = CssCursor.Default; - - public RazorViewTopLevelImpl(AvaloniaView avaloniaView) - { - _avaloniaView = avaloniaView; - TransparencyLevel = WindowTransparencyLevel.None; - AcrylicCompensationLevels = new AcrylicPlatformCompensationLevels(1, 1, 1); - _touchDevice = new TouchDevice(); - _penDevice = new PenDevice(); - } - - public ulong Timestamp => (ulong)_sw.ElapsedMilliseconds; - - - internal void SetSurface(GRContext context, SKHtmlCanvasInterop.GLInfo glInfo, SKColorType colorType, PixelSize size, double scaling) - { - _currentSurface = - new BlazorSkiaSurface(context, glInfo, colorType, size, scaling, GRSurfaceOrigin.BottomLeft); - } - - internal void SetSurface(SKColorType colorType, PixelSize size, double scaling, Action blitCallback) - { - _currentSurface = new BlazorSkiaRasterSurface(colorType, size, scaling, blitCallback); - } - - public void SetClientSize(SKSize size, double dpi) - { - var newSize = new Size(size.Width, size.Height); - - if (Math.Abs(RenderScaling - dpi) > 0.0001) - { - if (_currentSurface is { }) - { - _currentSurface.Scaling = dpi; - } - - ScalingChanged?.Invoke(dpi); - } - - if (newSize != _clientSize) - { - _clientSize = newSize; - - if (_currentSurface is { }) - { - _currentSurface.Size = new PixelSize((int)size.Width, (int)size.Height); - } - - Resized?.Invoke(newSize, PlatformResizeReason.User); - } - } - - public void RawPointerEvent( - RawPointerEventType eventType, string pointerType, - RawPointerPoint p, RawInputModifiers modifiers, long touchPointId) - { - 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 RawPointerEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers) - { - RawPointerId = touchPointId - }; - - input.Invoke(args); - } - } - - private IPointerDevice GetPointerDevice(string pointerType) - { - return pointerType switch - { - "touch" => _touchDevice, - "pen" => _penDevice, - _ => MouseDevice - }; - } - - public void RawMouseWheelEvent(Point p, Vector v, RawInputModifiers modifiers) - { - if (_inputRoot is { }) - { - Input?.Invoke(new RawMouseWheelEventArgs(MouseDevice, Timestamp, _inputRoot, p, v, modifiers)); - } - } - - public bool RawKeyboardEvent(RawKeyEventType type, string code, string key, RawInputModifiers modifiers) - { - if (Keycodes.KeyCodes.TryGetValue(code, out var avkey)) - { - if (_inputRoot is { }) - { - var args = new RawKeyEventArgs(KeyboardDevice, Timestamp, _inputRoot, type, avkey, modifiers); - - Input?.Invoke(args); - - return args.Handled; - } - } - else if (Keycodes.KeyCodes.TryGetValue(key, out avkey)) - { - if (_inputRoot is { }) - { - var args = new RawKeyEventArgs(KeyboardDevice, Timestamp, _inputRoot, type, avkey, modifiers); - - Input?.Invoke(args); - - return args.Handled; - } - } - - return false; - } - - public void RawTextEvent(string text) - { - if (_inputRoot is { }) - { - Input?.Invoke(new RawTextInputEventArgs(KeyboardDevice, Timestamp, _inputRoot, text)); - } - } - - public void Dispose() - { - - } - - public IRenderer CreateRenderer(IRenderRoot root) - { - var loop = AvaloniaLocator.Current.GetRequiredService(); - return new CompositingRenderer(root, new Compositor(loop, null)); - } - - public void Invalidate(Rect rect) - { - //Console.WriteLine("invalidate rect called"); - } - - public void SetInputRoot(IInputRoot inputRoot) - { - _inputRoot = inputRoot; - } - - public Point PointToClient(PixelPoint point) => new Point(point.X, point.Y); - - public PixelPoint PointToScreen(Point point) => new PixelPoint((int)point.X, (int)point.Y); - - public void SetCursor(ICursorImpl? cursor) - { - var val = (cursor as CssCursor)?.Value ?? CssCursor.Default; - if (_currentCursor != val) - { - SetCssCursor?.Invoke(val); - _currentCursor = val; - } - } - - public IPopupImpl? CreatePopup() - { - return null; - } - - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) - { - - } - - public Size ClientSize => _clientSize; - public Size? FrameSize => null; - public double RenderScaling => _currentSurface?.Scaling ?? 1; - - public IEnumerable Surfaces => new object[] { _currentSurface! }; - - public Action? SetCssCursor { get; set; } - public Action? Input { get; set; } - public Action? Paint { get; set; } - public Action? Resized { get; set; } - public Action? ScalingChanged { get; set; } - public Action? TransparencyLevelChanged { get; set; } - public Action? Closed { get; set; } - public Action? LostFocus { get; set; } - public IMouseDevice MouseDevice { get; } = new MouseDevice(); - - public IKeyboardDevice KeyboardDevice { get; } = BlazorWindowingPlatform.Keyboard; - public WindowTransparencyLevel TransparencyLevel { get; } - public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } - - public ITextInputMethodImpl TextInputMethod => _avaloniaView; - - public INativeControlHostImpl? NativeControlHost => _avaloniaView.GetNativeControlHostImpl(); - public IStorageProvider StorageProvider => _avaloniaView.GetStorageProvider(); - } -} diff --git a/src/Web/Avalonia.Web.Blazor/WinStubs.cs b/src/Web/Avalonia.Web.Blazor/WinStubs.cs deleted file mode 100644 index 808d1526d3..0000000000 --- a/src/Web/Avalonia.Web.Blazor/WinStubs.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Controls.Platform; -using Avalonia.Input; -using Avalonia.Input.Platform; -using Avalonia.Platform; - -#nullable enable - -namespace Avalonia.Web.Blazor -{ - internal class IconLoaderStub : IPlatformIconLoader - { - private class IconStub : IWindowIconImpl - { - public void Save(Stream outputStream) - { - - } - } - - public IWindowIconImpl LoadIcon(string fileName) => new IconStub(); - - public IWindowIconImpl LoadIcon(Stream stream) => new IconStub(); - - public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) => new IconStub(); - } - - internal class ScreenStub : IScreenImpl - { - public int ScreenCount => 1; - - public IReadOnlyList AllScreens { get; } = - new[] { new Screen(96, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) }; - - public Screen? ScreenFromPoint(PixelPoint point) - { - return ScreenHelper.ScreenFromPoint(point, AllScreens); - } - - public Screen? ScreenFromRect(PixelRect rect) - { - return ScreenHelper.ScreenFromRect(rect, AllScreens); - } - - public Screen? ScreenFromWindow(IWindowBaseImpl window) - { - return ScreenHelper.ScreenFromWindow(window, AllScreens); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs b/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs deleted file mode 100644 index 46c05d8e8c..0000000000 --- a/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs +++ /dev/null @@ -1,106 +0,0 @@ -using Avalonia.Controls.Platform; -using Avalonia.Input; -using Avalonia.Input.Platform; -using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.Threading; - -#nullable enable - -namespace Avalonia.Web.Blazor -{ - public class BlazorWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface - { - private bool _signaled; - private static KeyboardDevice? s_keyboard; - - public IWindowImpl CreateWindow() => throw new NotSupportedException(); - - IWindowImpl IWindowingPlatform.CreateEmbeddableWindow() - { - throw new NotImplementedException(); - } - - public ITrayIconImpl? CreateTrayIcon() - { - return null; - } - - public static KeyboardDevice Keyboard => s_keyboard ?? - throw new InvalidOperationException("BlazorWindowingPlatform not registered."); - - public static void Register() - { - var instance = new BlazorWindowingPlatform(); - s_keyboard = new KeyboardDevice(); - AvaloniaLocator.CurrentMutable - .Bind().ToSingleton() - .Bind().ToSingleton() - .Bind().ToConstant(s_keyboard) - .Bind().ToConstant(instance) - .Bind().ToConstant(instance) - .Bind().ToConstant(new RenderLoop()) - .Bind().ToConstant(ManualTriggerRenderTimer.Instance) - .Bind().ToConstant(instance) - .Bind().ToSingleton() - .Bind().ToSingleton(); - } - - public Size DoubleClickSize { get; } = new Size(2, 2); - - public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); - - public Size TouchDoubleClickSize => new Size(16, 16); - - public TimeSpan TouchDoubleClickTime => DoubleClickTime; - public void RunLoop(CancellationToken cancellationToken) - { - throw new NotSupportedException(); - } - - public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) - { - return GetRuntimePlatform() - .StartSystemTimer(interval, () => - { - Dispatcher.UIThread.RunJobs(priority); - tick(); - }); - } - - public void Signal(DispatcherPriority priority) - { - if (_signaled) - return; - - _signaled = true; - - IDisposable? disp = null; - - disp = GetRuntimePlatform() - .StartSystemTimer(TimeSpan.FromMilliseconds(1), - () => - { - _signaled = false; - disp?.Dispose(); - - Signaled?.Invoke(null); - }); - } - - public bool CurrentThreadIsLoopThread - { - get - { - return true; // Blazor is single threaded. - } - } - - public event Action? Signaled; - - private static IRuntimePlatform GetRuntimePlatform() - { - return AvaloniaLocator.Current.GetRequiredService(); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/_Imports.razor b/src/Web/Avalonia.Web.Blazor/_Imports.razor deleted file mode 100644 index 77285129da..0000000000 --- a/src/Web/Avalonia.Web.Blazor/_Imports.razor +++ /dev/null @@ -1 +0,0 @@ -@using Microsoft.AspNetCore.Components.Web diff --git a/src/Web/Avalonia.Web.Blazor/webapp/build.js b/src/Web/Avalonia.Web.Blazor/webapp/build.js deleted file mode 100644 index 9d585dcade..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/build.js +++ /dev/null @@ -1,16 +0,0 @@ -require("esbuild").build({ - entryPoints: [ - "./modules/Avalonia.ts", - "./modules/Storage.ts" - ], - outdir: "../wwwroot", - bundle: true, - minify: true, - format: "esm", - target: "es2016", - platform: "browser", - sourcemap: "linked", - loader: {".ts": "ts"} - }) - .then(() => console.log("⚡ Done")) - .catch(() => process.exit(1)); \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia.ts deleted file mode 100644 index 369f628a44..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { DpiWatcher } from "./Avalonia/DpiWatcher" -export { InputHelper } from "./Avalonia/InputHelper" -export { FocusHelper } from "./Avalonia/FocusHelper" -export { NativeControlHost } from "./Avalonia/NativeControlHost" -export { SizeWatcher } from "./Avalonia/SizeWatcher" -export { SKHtmlCanvas } from "./Avalonia/SKHtmlCanvas" -export { CaretHelper } from "./Avalonia/CaretHelper" diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts deleted file mode 100644 index 5709854087..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts +++ /dev/null @@ -1,149 +0,0 @@ -// Based on https://github.com/component/textarea-caret-position/blob/master/index.js -export class CaretHelper { - public static getCaretCoordinates( - element: HTMLInputElement | HTMLTextAreaElement, - position: number, - options?: { debug: boolean } - ) { - if (!isBrowser) { - throw new Error( - "textarea-caret-position#getCaretCoordinates should only be called in a browser" - ); - } - - const debug = (options && options.debug) || false; - if (debug) { - const el = document.querySelector( - "#input-textarea-caret-position-mirror-div" - ); - if (el) el.parentNode?.removeChild(el); - } - - // The mirror div will replicate the textarea's style - const div = document.createElement("div"); - div.id = "input-textarea-caret-position-mirror-div"; - document.body.appendChild(div); - - const style = div.style; - const computed = window.getComputedStyle - ? window.getComputedStyle(element) - : ((element as any)["currentStyle"] as CSSStyleDeclaration); // currentStyle for IE < 9 - const isInput = element.nodeName === "INPUT"; - - // Default textarea styles - style.whiteSpace = "pre-wrap"; - if (!isInput) style.wordWrap = "break-word"; // only for textarea-s - - // Position off-screen - style.position = "absolute"; // required to return coordinates properly - if (!debug) style.visibility = "hidden"; // not 'display: none' because we want rendering - - // Transfer the element's properties to the div - properties.forEach((prop: string) => { - if (isInput && prop === "lineHeight") { - // Special case for s because text is rendered centered and line height may be != height - if (computed.boxSizing === "border-box") { - const height = parseInt(computed.height); - const outerHeight = - parseInt(computed.paddingTop) + - parseInt(computed.paddingBottom) + - parseInt(computed.borderTopWidth) + - parseInt(computed.borderBottomWidth); - const targetHeight = outerHeight + parseInt(computed.lineHeight); - if (height > targetHeight) { - style.lineHeight = height - outerHeight + "px"; - } else if (height === targetHeight) { - style.lineHeight = computed.lineHeight; - } else { - style.lineHeight = "0"; - } - } else { - style.lineHeight = computed.height; - } - } else { - (style as any)[prop] = (computed as any)[prop]; - } - }); - - if (isFirefox) { - // Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275 - if (element.scrollHeight > parseInt(computed.height)) - style.overflowY = "scroll"; - } else { - style.overflow = "hidden"; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll' - } - - div.textContent = element.value.substring(0, position); - // The second special handling for input type="text" vs textarea: - // spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037 - if (isInput) div.textContent = div.textContent.replace(/\s/g, "\u00a0"); - - const span = document.createElement("span"); - // Wrapping must be replicated *exactly*, including when a long word gets - // onto the next line, with whitespace at the end of the line before (#7). - // The *only* reliable way to do that is to copy the *entire* rest of the - // textarea's content into the created at the caret position. - // For inputs, just '.' would be enough, but no need to bother. - span.textContent = element.value.substring(position) || "."; // || because a completely empty faux span doesn't render at all - div.appendChild(span); - - const coordinates = { - top: span.offsetTop + parseInt(computed["borderTopWidth"]), - left: span.offsetLeft + parseInt(computed["borderLeftWidth"]), - height: parseInt(computed["lineHeight"]), - }; - - if (debug) { - span.style.backgroundColor = "#aaa"; - } else { - document.body.removeChild(div); - } - - return coordinates; - } -} - - -var properties = [ - "direction", // RTL support - "boxSizing", - "width", // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does - "height", - "overflowX", - "overflowY", // copy the scrollbar for IE - - "borderTopWidth", - "borderRightWidth", - "borderBottomWidth", - "borderLeftWidth", - "borderStyle", - - "paddingTop", - "paddingRight", - "paddingBottom", - "paddingLeft", - - // https://developer.mozilla.org/en-US/docs/Web/CSS/font - "fontStyle", - "fontVariant", - "fontWeight", - "fontStretch", - "fontSize", - "fontSizeAdjust", - "lineHeight", - "fontFamily", - - "textAlign", - "textTransform", - "textIndent", - "textDecoration", // might not make a difference, but better be safe - - "letterSpacing", - "wordSpacing", - - "tabSize", - "MozTabSize", -]; - -const isBrowser = typeof window !== "undefined"; -const isFirefox = isBrowser && (window as any).mozInnerScreenX != null; diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/DpiWatcher.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/DpiWatcher.ts deleted file mode 100644 index 06235782f8..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/DpiWatcher.ts +++ /dev/null @@ -1,40 +0,0 @@ -export class DpiWatcher { - static lastDpi: number; - static timerId: number; - static callback?: DotNet.DotNetObject; - - public static getDpi() { - return window.devicePixelRatio; - } - - public static start(callback: DotNet.DotNetObject): number { - //console.info(`Starting DPI watcher with callback ${callback._id}...`); - - DpiWatcher.lastDpi = window.devicePixelRatio; - DpiWatcher.timerId = window.setInterval(DpiWatcher.update, 1000); - DpiWatcher.callback = callback; - - return DpiWatcher.lastDpi; - } - - public static stop() { - //console.info(`Stopping DPI watcher with callback ${DpiWatcher.callback._id}...`); - - window.clearInterval(DpiWatcher.timerId); - - DpiWatcher.callback = undefined; - } - - static update() { - if (!DpiWatcher.callback) - return; - - const currentDpi = window.devicePixelRatio; - const lastDpi = DpiWatcher.lastDpi; - DpiWatcher.lastDpi = currentDpi; - - if (Math.abs(lastDpi - currentDpi) > 0.001) { - DpiWatcher.callback.invokeMethod('Invoke', lastDpi, currentDpi); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/FocusHelper.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/FocusHelper.ts deleted file mode 100644 index 96ffee3d53..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/FocusHelper.ts +++ /dev/null @@ -1,9 +0,0 @@ -export class FocusHelper { - public static focus(inputElement: HTMLElement) { - inputElement.focus(); - } - - public static setCursor(inputElement: HTMLInputElement, kind: string) { - inputElement.style.cursor = kind; - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts deleted file mode 100644 index 1ea85d84e3..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts +++ /dev/null @@ -1,86 +0,0 @@ -import {CaretHelper} from "./CaretHelper"; - -export class InputHelper { - static inputCallback?: DotNet.DotNetObject; - static compositionCallback?: DotNet.DotNetObject - - public static start(inputElement: HTMLInputElement, compositionCallback: DotNet.DotNetObject, inputCallback: DotNet.DotNetObject) - { - InputHelper.compositionCallback = compositionCallback; - - inputElement.addEventListener('compositionstart', InputHelper.onCompositionEvent); - inputElement.addEventListener('compositionupdate', InputHelper.onCompositionEvent); - inputElement.addEventListener('compositionend', InputHelper.onCompositionEvent); - - InputHelper.inputCallback = inputCallback; - - inputElement.addEventListener('input', InputHelper.onInputEvent); - } - - public static clear(inputElement: HTMLInputElement) { - inputElement.value = ""; - } - public static focus(inputElement: HTMLInputElement) { - inputElement.focus(); - } - - public static setCursor(inputElement: HTMLInputElement, kind: string) { - inputElement.style.cursor = kind; - } - - public static setBounds(inputElement: HTMLInputElement, x: number, y: number, caretWidth: number, caretHeight: number, caret: number) { - inputElement.style.left = (x).toFixed(0) + "px"; - inputElement.style.top = (y).toFixed(0) + "px"; - - let {height, left, top} = CaretHelper.getCaretCoordinates(inputElement, caret); - - inputElement.style.left = (x - left).toFixed(0) + "px"; - inputElement.style.top = (y - top).toFixed(0) + "px"; - } - - public static hide(inputElement: HTMLInputElement) { - inputElement.style.display = 'none'; - } - - public static show(inputElement: HTMLInputElement) { - inputElement.style.display = 'block'; - } - - public static setSurroundingText(inputElement: HTMLInputElement, text: string, start: number, end: number) { - if (!inputElement) { - return; - } - - inputElement.value = text; - inputElement.setSelectionRange(start, end); - inputElement.style.width = "20px"; - inputElement.style.width = inputElement.scrollWidth + "px"; - } - - private static onCompositionEvent(ev: CompositionEvent) - { - if(!InputHelper.compositionCallback) - return; - - switch (ev.type) - { - case "compositionstart": - case "compositionupdate": - case "compositionend": - InputHelper.compositionCallback.invokeMethod('Invoke', ev.type, ev.data); - break; - } - } - - private static onInputEvent(ev: Event) { - if (!InputHelper.inputCallback) - return; - - var inputEvent = ev as InputEvent; - - InputHelper.inputCallback.invokeMethod('Invoke', ev.type, inputEvent.data); - } -} - - - diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/NativeControlHost.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/NativeControlHost.ts deleted file mode 100644 index 9e5c3843c8..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/NativeControlHost.ts +++ /dev/null @@ -1,61 +0,0 @@ -export class NativeControlHost { - public static CreateDefaultChild(parent: HTMLElement): HTMLElement { - return document.createElement("div"); - } - - // Used to convert ElementReference to JSObjectReference. - // Is there a better way? - public static GetReference(element: Element): Element { - return element; - } - - public static CreateAttachment(): NativeControlHostTopLevelAttachment { - return new NativeControlHostTopLevelAttachment(); - } -} - -class NativeControlHostTopLevelAttachment { - _child?: HTMLElement; - _host?: HTMLElement; - - InitializeWithChildHandle(child: HTMLElement) { - this._child = child; - this._child.style.position = "absolute"; - } - - AttachTo(host: HTMLElement): void { - if (this._host && this._child) { - this._host.removeChild(this._child); - } - - this._host = host; - - if (this._host && this._child) { - this._host.appendChild(this._child); - } - } - - ShowInBounds(x: number, y: number, width: number, height: number): void { - if (this._child) { - this._child.style.top = y + "px"; - this._child.style.left = x + "px"; - this._child.style.width = width + "px"; - this._child.style.height = height + "px"; - this._child.style.display = "block"; - } - } - - HideWithSize(width: number, height: number): void { - if (this._child) { - this._child.style.width = width + "px"; - this._child.style.height = height + "px"; - this._child.style.display = "none"; - } - } - - ReleaseChild(): void { - if (this._child) { - this._child = undefined; - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SKHtmlCanvas.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SKHtmlCanvas.ts deleted file mode 100644 index e934f74807..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SKHtmlCanvas.ts +++ /dev/null @@ -1,255 +0,0 @@ -// aliases for emscripten -declare let GL: any; -declare let GLctx: WebGLRenderingContext; -declare let Module: EmscriptenModule; - -// container for gl info -type SKGLViewInfo = { - context: WebGLRenderingContext | WebGL2RenderingContext | undefined; - fboId: number; - stencil: number; - sample: number; - depth: number; -} - -// alias for a potential skia html canvas -type SKHtmlCanvasElement = { - SKHtmlCanvas: SKHtmlCanvas | undefined -} & HTMLCanvasElement - -export class SKHtmlCanvas { - static elements: Map; - - htmlCanvas: HTMLCanvasElement; - glInfo?: SKGLViewInfo; - renderFrameCallback: DotNet.DotNetObject; - renderLoopEnabled: boolean = false; - renderLoopRequest: number = 0; - newWidth?: number; - newHeight?: number; - - public static initGL(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): SKGLViewInfo | null { - var view = SKHtmlCanvas.init(true, element, elementId, callback); - if (!view || !view.glInfo) - return null; - - return view.glInfo; - } - - public static initRaster(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): boolean { - var view = SKHtmlCanvas.init(false, element, elementId, callback); - if (!view) - return false; - - return true; - } - - static init(useGL: boolean, element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): SKHtmlCanvas | null { - var htmlCanvas = element as SKHtmlCanvasElement; - if (!htmlCanvas) { - console.error(`No canvas element was provided.`); - return null; - } - - if (!SKHtmlCanvas.elements) - SKHtmlCanvas.elements = new Map(); - SKHtmlCanvas.elements.set(elementId, element); - - const view = new SKHtmlCanvas(useGL, element, callback); - - htmlCanvas.SKHtmlCanvas = view; - - return view; - } - - public static deinit(elementId: string) { - if (!elementId) - return; - - const element = SKHtmlCanvas.elements.get(elementId); - SKHtmlCanvas.elements.delete(elementId); - - const htmlCanvas = element as SKHtmlCanvasElement; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - - htmlCanvas.SKHtmlCanvas.deinit(); - htmlCanvas.SKHtmlCanvas = undefined; - } - - public static requestAnimationFrame(element: HTMLCanvasElement, renderLoop?: boolean) { - const htmlCanvas = element as SKHtmlCanvasElement; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - - htmlCanvas.SKHtmlCanvas.requestAnimationFrame(renderLoop); - } - - public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number) { - const htmlCanvas = element as SKHtmlCanvasElement; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - - htmlCanvas.SKHtmlCanvas.setCanvasSize(width, height); - } - - public static setEnableRenderLoop(element: HTMLCanvasElement, enable: boolean) { - const htmlCanvas = element as SKHtmlCanvasElement; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - - htmlCanvas.SKHtmlCanvas.setEnableRenderLoop(enable); - } - - public static putImageData(element: HTMLCanvasElement, pData: number, width: number, height: number) { - const htmlCanvas = element as SKHtmlCanvasElement; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - - htmlCanvas.SKHtmlCanvas.putImageData(pData, width, height); - } - - public constructor(useGL: boolean, element: HTMLCanvasElement, callback: DotNet.DotNetObject) { - this.htmlCanvas = element; - this.renderFrameCallback = callback; - - if (useGL) { - const ctx = SKHtmlCanvas.createWebGLContext(this.htmlCanvas); - if (!ctx) { - console.error(`Failed to create WebGL context: err ${ctx}`); - return; - } - - // make current - GL.makeContextCurrent(ctx); - - // read values - const fbo = GLctx.getParameter(GLctx.FRAMEBUFFER_BINDING); - this.glInfo = { - context: ctx, - fboId: fbo ? fbo.id : 0, - stencil: GLctx.getParameter(GLctx.STENCIL_BITS), - sample: 0, // TODO: GLctx.getParameter(GLctx.SAMPLES) - depth: GLctx.getParameter(GLctx.DEPTH_BITS), - }; - } - } - - public deinit() { - this.setEnableRenderLoop(false); - } - - public setCanvasSize(width: number, height: number) { - this.newWidth = width; - this.newHeight = height; - - if (this.htmlCanvas.width != this.newWidth) { - this.htmlCanvas.width = this.newWidth; - } - - if (this.htmlCanvas.height != this.newHeight) { - this.htmlCanvas.height = this.newHeight; - } - - if (this.glInfo) { - // make current - GL.makeContextCurrent(this.glInfo.context); - } - } - - public requestAnimationFrame(renderLoop?: boolean) { - // optionally update the render loop - if (renderLoop !== undefined && this.renderLoopEnabled !== renderLoop) - this.setEnableRenderLoop(renderLoop); - - // skip because we have a render loop - if (this.renderLoopRequest !== 0) - return; - - // add the draw to the next frame - this.renderLoopRequest = window.requestAnimationFrame(() => { - if (this.glInfo) { - // make current - GL.makeContextCurrent(this.glInfo.context); - } - - if (this.htmlCanvas.width != this.newWidth) { - this.htmlCanvas.width = this.newWidth || 0; - } - - if (this.htmlCanvas.height != this.newHeight) { - this.htmlCanvas.height = this.newHeight || 0; - } - - this.renderFrameCallback.invokeMethod('Invoke'); - this.renderLoopRequest = 0; - - // we may want to draw the next frame - if (this.renderLoopEnabled) - this.requestAnimationFrame(); - }); - } - - public setEnableRenderLoop(enable: boolean) { - this.renderLoopEnabled = enable; - - // either start the new frame or cancel the existing one - if (enable) { - //console.info(`Enabling render loop with callback ${this.renderFrameCallback._id}...`); - this.requestAnimationFrame(); - } else if (this.renderLoopRequest !== 0) { - window.cancelAnimationFrame(this.renderLoopRequest); - this.renderLoopRequest = 0; - } - } - - public putImageData(pData: number, width: number, height: number): boolean { - if (this.glInfo || !pData || width <= 0 || width <= 0) - return false; - - var ctx = this.htmlCanvas.getContext('2d'); - if (!ctx) { - console.error(`Failed to obtain 2D canvas context.`); - return false; - } - - // make sure the canvas is scaled correctly for the drawing - this.htmlCanvas.width = width; - this.htmlCanvas.height = height; - - // set the canvas to be the bytes - var buffer = new Uint8ClampedArray(Module.HEAPU8.buffer, pData, width * height * 4); - var imageData = new ImageData(buffer, width, height); - ctx.putImageData(imageData, 0, 0); - - return true; - } - - static createWebGLContext(htmlCanvas: HTMLCanvasElement): WebGLRenderingContext | WebGL2RenderingContext { - const contextAttributes = { - alpha: 1, - depth: 1, - stencil: 8, - antialias: 0, - premultipliedAlpha: 1, - preserveDrawingBuffer: 0, - preferLowPowerToHighPerformance: 0, - failIfMajorPerformanceCaveat: 0, - majorVersion: 2, - minorVersion: 0, - enableExtensionsByDefault: 1, - explicitSwapControl: 0, - renderViaOffscreenBackBuffer: 1, - }; - - let ctx: WebGLRenderingContext = GL.createContext(htmlCanvas, contextAttributes); - if (!ctx && contextAttributes.majorVersion > 1) { - console.warn('Falling back to WebGL 1.0'); - contextAttributes.majorVersion = 1; - contextAttributes.minorVersion = 0; - ctx = GL.createContext(htmlCanvas, contextAttributes); - } - - return ctx; - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SizeWatcher.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SizeWatcher.ts deleted file mode 100644 index 715b252988..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SizeWatcher.ts +++ /dev/null @@ -1,67 +0,0 @@ -type SizeWatcherElement = { - SizeWatcher: SizeWatcherInstance; -} & HTMLElement - -type SizeWatcherInstance = { - callback: DotNet.DotNetObject; -} - -export class SizeWatcher { - static observer: ResizeObserver; - static elements: Map; - - public static observe(element: HTMLElement, elementId: string, callback: DotNet.DotNetObject) { - if (!element || !callback) - return; - - //console.info(`Adding size watcher observation with callback ${callback._id}...`); - - SizeWatcher.init(); - - const watcherElement = element as SizeWatcherElement; - watcherElement.SizeWatcher = { - callback: callback - }; - - SizeWatcher.elements.set(elementId, element); - SizeWatcher.observer.observe(element); - - SizeWatcher.invoke(element); - } - - public static unobserve(elementId: string) { - if (!elementId || !SizeWatcher.observer) - return; - - //console.info('Removing size watcher observation...'); - - const element = SizeWatcher.elements.get(elementId)!; - - SizeWatcher.elements.delete(elementId); - SizeWatcher.observer.unobserve(element); - } - - static init() { - if (SizeWatcher.observer) - return; - - //console.info('Starting size watcher...'); - - SizeWatcher.elements = new Map(); - SizeWatcher.observer = new ResizeObserver((entries) => { - for (let entry of entries) { - SizeWatcher.invoke(entry.target); - } - }); - } - - static invoke(element: Element) { - const watcherElement = element as SizeWatcherElement; - const instance = watcherElement.SizeWatcher; - - if (!instance || !instance.callback) - return; - - return instance.callback.invokeMethod('Invoke', element.clientWidth, element.clientHeight); - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage.ts deleted file mode 100644 index 2c4654e81b..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage.ts +++ /dev/null @@ -1 +0,0 @@ -export { StorageProvider } from "./Storage/StorageProvider" diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/IndexedDbWrapper.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/IndexedDbWrapper.ts deleted file mode 100644 index 2eaa8de2fe..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/IndexedDbWrapper.ts +++ /dev/null @@ -1,79 +0,0 @@ -class InnerDbConnection { - constructor(private database: IDBDatabase) { } - - private openStore(store: string, mode: IDBTransactionMode): IDBObjectStore { - const tx = this.database.transaction(store, mode); - return tx.objectStore(store); - } - - public put(store: string, obj: any, key?: IDBValidKey): Promise { - const os = this.openStore(store, "readwrite"); - - return new Promise((resolve, reject) => { - const response = os.put(obj, key); - response.onsuccess = () => { - resolve(response.result); - }; - response.onerror = () => { - reject(response.error); - }; - }); - } - - public get(store: string, key: IDBValidKey): any { - const os = this.openStore(store, "readonly"); - - return new Promise((resolve, reject) => { - const response = os.get(key); - response.onsuccess = () => { - resolve(response.result); - }; - response.onerror = () => { - reject(response.error); - }; - }); - } - - public delete(store: string, key: IDBValidKey): Promise { - const os = this.openStore(store, "readwrite"); - - return new Promise((resolve, reject) => { - const response = os.delete(key); - response.onsuccess = () => { - resolve(); - }; - response.onerror = () => { - reject(response.error); - }; - }); - } - - public close() { - this.database.close(); - } -} - -export class IndexedDbWrapper { - constructor(private databaseName: string, private objectStores: [string]) { - } - - public connect(): Promise { - const conn = window.indexedDB.open(this.databaseName, 1); - - conn.onupgradeneeded = event => { - const db = (>event.target).result; - this.objectStores.forEach(store => { - db.createObjectStore(store); - }); - }; - - return new Promise((resolve, reject) => { - conn.onsuccess = event => { - resolve(new InnerDbConnection((>event.target).result)); - }; - conn.onerror = event => { - reject((>event.target).error); - }; - }); - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts deleted file mode 100644 index 896e174e43..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { IndexedDbWrapper } from "./IndexedDbWrapper"; - -declare global { - type WellKnownDirectory = "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos"; - type StartInDirectory = WellKnownDirectory | FileSystemHandle; - interface OpenFilePickerOptions { - startIn?: StartInDirectory - } - interface SaveFilePickerOptions { - startIn?: StartInDirectory - } -} - -const fileBookmarksStore: string = "fileBookmarks"; -const avaloniaDb = new IndexedDbWrapper("AvaloniaDb", [ - fileBookmarksStore -]); - -class StorageItem { - constructor(public handle: FileSystemHandle, private bookmarkId?: string) { } - - public getName(): string { - return this.handle.name - } - - public getKind(): string { - return this.handle.kind; - } - - public async openRead(): Promise { - if (!(this.handle instanceof FileSystemFileHandle)) { - throw new Error("StorageItem is not a file"); - } - - await this.verityPermissions('read'); - - const file = await this.handle.getFile(); - return file; - } - - public async openWrite(): Promise { - if (!(this.handle instanceof FileSystemFileHandle)) { - throw new Error("StorageItem is not a file"); - } - - await this.verityPermissions('readwrite'); - - return await this.handle.createWritable({ keepExistingData: true }); - } - - public async getProperties(): Promise<{ Size: number, LastModified: number, Type: string } | null> { - const file = this.handle instanceof FileSystemFileHandle - && await this.handle.getFile(); - - if (!file) { - return null; - } - - return { - Size: file.size, - LastModified: file.lastModified, - Type: file.type - } - } - - public async getItems(): Promise { - if (this.handle.kind !== "directory"){ - return new StorageItems([]); - } - - const items: StorageItem[] = []; - for await (const [key, value] of (this.handle as any).entries()) { - items.push(new StorageItem(value)); - } - return new StorageItems(items); - } - - private async verityPermissions(mode: FileSystemPermissionMode): Promise { - if (await this.handle.queryPermission({ mode }) === 'granted') { - return; - } - - if (await this.handle.requestPermission({ mode }) === "denied") { - throw new Error("Read permissions denied"); - } - } - - public async saveBookmark(): Promise { - // If file was previously bookmarked, just return old one. - if (this.bookmarkId) { - return this.bookmarkId; - } - - const connection = await avaloniaDb.connect(); - try { - const key = await connection.put(fileBookmarksStore, this.handle, this.generateBookmarkId()); - return key; - } - finally { - connection.close(); - } - } - - public async deleteBookmark(): Promise { - if (!this.bookmarkId) { - return; - } - - const connection = await avaloniaDb.connect(); - try { - const key = await connection.delete(fileBookmarksStore, this.bookmarkId); - } - finally { - connection.close(); - } - } - - private generateBookmarkId(): string { - return Date.now().toString(36) + Math.random().toString(36).substring(2); - } -} - -class StorageItems { - constructor(private items: StorageItem[]) { } - - public count(): number { - return this.items.length; - } - - public at(index: number): StorageItem { - return this.items[index]; - } -} - -export class StorageProvider { - - public static canOpen(): boolean { - return typeof window.showOpenFilePicker !== 'undefined'; - } - - public static canSave(): boolean { - return typeof window.showSaveFilePicker !== 'undefined'; - } - - public static canPickFolder(): boolean { - return typeof window.showDirectoryPicker !== 'undefined'; - } - - public static async selectFolderDialog( - startIn: StorageItem | null) - : Promise { - - // 'Picker' API doesn't accept "null" as a parameter, so it should be set to undefined. - const options: DirectoryPickerOptions = { - startIn: (startIn?.handle || undefined) - }; - - const handle = await window.showDirectoryPicker(options); - return new StorageItem(handle); - } - - public static async openFileDialog( - startIn: StorageItem | null, multiple: boolean, - types: FilePickerAcceptType[] | null, excludeAcceptAllOption: boolean) - : Promise { - - const options: OpenFilePickerOptions = { - startIn: (startIn?.handle || undefined), - multiple, - excludeAcceptAllOption, - types: (types || undefined) - }; - - const handles = await window.showOpenFilePicker(options); - return new StorageItems(handles.map((handle: FileSystemHandle) => new StorageItem(handle))); - } - - public static async saveFileDialog( - startIn: StorageItem | null, suggestedName: string | null, - types: FilePickerAcceptType[] | null, excludeAcceptAllOption: boolean) - : Promise { - - const options: SaveFilePickerOptions = { - startIn: (startIn?.handle || undefined), - suggestedName: (suggestedName || undefined), - excludeAcceptAllOption, - types: (types || undefined) - }; - - const handle = await window.showSaveFilePicker(options); - return new StorageItem(handle); - } - - public static async openBookmark(key: string): Promise { - const connection = await avaloniaDb.connect(); - try { - const handle = await connection.get(fileBookmarksStore, key); - return handle && new StorageItem(handle, key); - } - finally { - connection.close(); - } - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/package.json b/src/Web/Avalonia.Web.Blazor/webapp/package.json deleted file mode 100644 index 27e15b0abd..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "avalonia.web", - "scripts": { - "prebuild": "tsc -noEmit", - "build": "node build.js" - }, - "devDependencies": { - "@types/emscripten": "^1.39.6", - "@types/wicg-file-system-access": "^2020.9.5", - "typescript": "^4.7.4", - "esbuild": "^0.15.7" - } -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/tsconfig.json b/src/Web/Avalonia.Web.Blazor/webapp/tsconfig.json deleted file mode 100644 index 4e90bc230d..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "target": "es2016", - "strict": true, - "sourceMap": true, - "outDir": "../wwwroot", - "noEmitOnError": true, - "isolatedModules": true, // we need it for esbuild - "lib": [ - "dom", - "es2016", - "esnext.asynciterable" - ] - }, - "exclude": [ - "node_modules" - ] -} diff --git a/src/Web/Avalonia.Web.Blazor/webapp/types/dotnet/index.d.ts b/src/Web/Avalonia.Web.Blazor/webapp/types/dotnet/index.d.ts deleted file mode 100644 index 932dfa1e1f..0000000000 --- a/src/Web/Avalonia.Web.Blazor/webapp/types/dotnet/index.d.ts +++ /dev/null @@ -1,56 +0,0 @@ -// Type definitions for non-npm package @blazor/javascript-interop 3.1 -// Project: https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interop?view=aspnetcore-3.1 -// Definitions by: Piotr Błażejewicz (Peter Blazejewicz) -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// Minimum TypeScript Version: 3.0 - -// Here be dragons! -// This is community-maintained definition file intended to ease the process of developing -// high quality JavaScript interop code to be used in Blazor application from your C# .NET code. -// Could be removed without a notice in case official definition types ships with Blazor itself. - -// tslint:disable:no-unnecessary-generics - -declare namespace DotNet { - /** - * Invokes the specified .NET public method synchronously. Not all hosting scenarios support - * synchronous invocation, so if possible use invokeMethodAsync instead. - * - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns The result of the operation. - */ - function invokeMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T; - /** - * Invokes the specified .NET public method asynchronously. - * - * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns A promise representing the result of the operation. - */ - function invokeMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise; - /** - * Represents the .NET instance passed by reference to JavaScript. - */ - interface DotNetObject { - /** - * Invokes the specified .NET instance public method synchronously. Not all hosting scenarios support - * synchronous invocation, so if possible use invokeMethodAsync instead. - * - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns The result of the operation. - */ - invokeMethod(methodIdentifier: string, ...args: any[]): T; - /** - * Invokes the specified .NET instance public method asynchronously. - * - * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. - * @param args Arguments to pass to the method, each of which must be JSON-serializable. - * @returns A promise representing the result of the operation. - */ - invokeMethodAsync(methodIdentifier: string, ...args: any[]): Promise; - } -} diff --git a/src/Web/Avalonia.Web.Sample/main.js b/src/Web/Avalonia.Web.Sample/main.js index 3683aea181..87f8a4f943 100644 --- a/src/Web/Avalonia.Web.Sample/main.js +++ b/src/Web/Avalonia.Web.Sample/main.js @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { dotnet } from './dotnet.js' -import { createAvaloniaRuntime } from './avalonia.js'; +import { registerAvaloniaModule } from './avalonia.js'; const is_browser = typeof window != "undefined"; if (!is_browser) throw new Error(`Expected to be running in a browser`); @@ -12,7 +12,7 @@ const dotnetRuntime = await dotnet .withApplicationArgumentsFromQuery() .create(); -await createAvaloniaRuntime(dotnetRuntime); +await registerAvaloniaModule(dotnetRuntime); const config = dotnetRuntime.getConfig(); diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index e81620ffde..a5da719912 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -40,13 +40,12 @@ namespace Avalonia.Web private static int _canvasCount; public AvaloniaView(string divId) + : this(DomHelper.GetElementById(divId) ?? throw new Exception($"Element with id {divId} was not found in the html document.")) { - var host = DomHelper.GetElementById(divId); - if (host == null) - { - throw new Exception($"Element with id {divId} was not found in the html document."); - } + } + public AvaloniaView(JSObject host) + { var hostContent = DomHelper.CreateAvaloniaHost(host); if (hostContent == null) { @@ -137,7 +136,7 @@ namespace Avalonia.Web _topLevelImpl.SetClientSize(_canvasSize, _dpi); - DomHelper.ObserveSize(host, divId, OnSizeChanged); + DomHelper.ObserveSize(host, null, OnSizeChanged); CanvasHelper.RequestAnimationFrame(_canvas, true); } @@ -387,7 +386,7 @@ namespace Avalonia.Web InputHelper.FocusElement(_containerElement); } - public void SetClient(ITextInputMethodClient? client) + void ITextInputMethodImpl.SetClient(ITextInputMethodClient? client) { Console.WriteLine("Set Client"); if (_client != null) @@ -431,18 +430,18 @@ namespace Avalonia.Web } } - public void SetCursorRect(Rect rect) + void ITextInputMethodImpl.SetCursorRect(Rect rect) { InputHelper.FocusElement(_inputElement); InputHelper.SetBounds(_inputElement, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height, _client?.SurroundingText.CursorOffset ?? 0); InputHelper.FocusElement(_inputElement); } - public void SetOptions(TextInputOptions options) + void ITextInputMethodImpl.SetOptions(TextInputOptions options) { } - public void Reset() + void ITextInputMethodImpl.Reset() { InputHelper.ClearInputElement(_inputElement); InputHelper.SetSurroundingText(_inputElement, "", 0, 0); diff --git a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs b/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs index 091ab3f68c..d962956567 100644 --- a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs +++ b/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs @@ -4,10 +4,11 @@ using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Media; using Avalonia.Web.Skia; +using System.Runtime.Versioning; namespace Avalonia.Web { - [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings + [SupportedOSPlatform("browser")] public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime { public AvaloniaView? View; @@ -19,23 +20,32 @@ namespace Avalonia.Web } } + [SupportedOSPlatform("browser")] public static partial class WebAppBuilder { public static T SetupBrowserApp( - this T builder, string mainDivId) - where T : AppBuilderBase, new() + this T builder, string mainDivId) + where T : AppBuilderBase, new() { var lifetime = new BrowserSingleViewLifetime(); return builder - .UseWindowingSubsystem(BrowserWindowingPlatform.Register) - .UseSkia() - .With(new SkiaOptions { CustomGpuFactory = () => new BrowserSkiaGpu() }) + .UseBrowser() .AfterSetup(b => { lifetime.View = new AvaloniaView(mainDivId); }) .SetupWithLifetime(lifetime); } + + public static T UseBrowser( + this T builder) + where T : AppBuilderBase, new() + { + return builder + .UseWindowingSubsystem(BrowserWindowingPlatform.Register) + .UseSkia() + .With(new SkiaOptions { CustomGpuFactory = () => new BrowserSkiaGpu() }); + } } } diff --git a/src/Web/Avalonia.Web/Cursor.cs b/src/Web/Avalonia.Web/Cursor.cs index 5db0bdeda2..af7098f800 100644 --- a/src/Web/Avalonia.Web/Cursor.cs +++ b/src/Web/Avalonia.Web/Cursor.cs @@ -5,7 +5,7 @@ using Avalonia.Platform; namespace Avalonia.Web { - public class CssCursor : ICursorImpl + internal class CssCursor : ICursorImpl { public const string Default = "default"; public string? Value { get; set; } diff --git a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs index 66a1d421b7..ff07d1757b 100644 --- a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs +++ b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs @@ -29,13 +29,13 @@ internal static partial class CanvasHelper return glInfo; } - [JSImport("Canvas.requestAnimationFrame", "avalonia.ts")] + [JSImport("Canvas.requestAnimationFrame", "avalonia")] public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop); - [JSImport("Canvas.setCanvasSize", "avalonia.ts")] + [JSImport("Canvas.setCanvasSize", "avalonia")] public static partial void SetCanvasSize(JSObject canvas, int height, int width); - [JSImport("Canvas.initGL", "avalonia.ts")] + [JSImport("Canvas.initGL", "avalonia")] private static partial JSObject InitGL( JSObject canvas, string canvasId, diff --git a/src/Web/Avalonia.Web/Interop/DomHelper.cs b/src/Web/Avalonia.Web/Interop/DomHelper.cs index c02bc4dae3..c2cf0a0c44 100644 --- a/src/Web/Avalonia.Web/Interop/DomHelper.cs +++ b/src/Web/Avalonia.Web/Interop/DomHelper.cs @@ -8,20 +8,20 @@ internal static partial class DomHelper [JSImport("globalThis.document.getElementById")] internal static partial JSObject? GetElementById(string id); - [JSImport("AvaloniaDOM.createAvaloniaHost", "avalonia.ts")] + [JSImport("AvaloniaDOM.createAvaloniaHost", "avalonia")] public static partial JSObject CreateAvaloniaHost(JSObject element); - [JSImport("AvaloniaDOM.addClass", "avalonia.ts")] + [JSImport("AvaloniaDOM.addClass", "avalonia")] public static partial void AddCssClass(JSObject element, string className); - [JSImport("SizeWatcher.observe", "avalonia.ts")] + [JSImport("SizeWatcher.observe", "avalonia")] public static partial JSObject ObserveSize( JSObject canvas, - string canvasId, + string? canvasId, [JSMarshalAs>] Action onSizeChanged); - [JSImport("DpiWatcher.start", "avalonia.ts")] + [JSImport("DpiWatcher.start", "avalonia")] public static partial double ObserveDpi( [JSMarshalAs>] Action onDpiChanged); diff --git a/src/Web/Avalonia.Web/Interop/InputHelper.cs b/src/Web/Avalonia.Web/Interop/InputHelper.cs index b32b6566f2..bdd1957e03 100644 --- a/src/Web/Avalonia.Web/Interop/InputHelper.cs +++ b/src/Web/Avalonia.Web/Interop/InputHelper.cs @@ -6,7 +6,7 @@ namespace Avalonia.Web.Interop; internal static partial class InputHelper { - [JSImport("InputHelper.subscribeKeyEvents", "avalonia.ts")] + [JSImport("InputHelper.subscribeKeyEvents", "avalonia")] public static partial void SubscribeKeyEvents( JSObject htmlElement, [JSMarshalAs>] @@ -14,7 +14,7 @@ internal static partial class InputHelper [JSMarshalAs>] Func keyUp); - [JSImport("InputHelper.subscribeTextEvents", "avalonia.ts")] + [JSImport("InputHelper.subscribeTextEvents", "avalonia")] public static partial void SubscribeTextEvents( JSObject htmlElement, [JSMarshalAs>] @@ -26,7 +26,7 @@ internal static partial class InputHelper [JSMarshalAs>] Func onCompositionEnd); - [JSImport("InputHelper.subscribePointerEvents", "avalonia.ts")] + [JSImport("InputHelper.subscribePointerEvents", "avalonia")] public static partial void SubscribePointerEvents( JSObject htmlElement, [JSMarshalAs>] @@ -39,35 +39,35 @@ internal static partial class InputHelper Func wheel); - [JSImport("InputHelper.subscribeInputEvents", "avalonia.ts")] + [JSImport("InputHelper.subscribeInputEvents", "avalonia")] public static partial void SubscribeInputEvents( JSObject htmlElement, [JSMarshalAs>] Func input); - [JSImport("InputHelper.clearInput", "avalonia.ts")] + [JSImport("InputHelper.clearInput", "avalonia")] public static partial void ClearInputElement(JSObject htmlElement); - [JSImport("InputHelper.isInputElement", "avalonia.ts")] + [JSImport("InputHelper.isInputElement", "avalonia")] public static partial void IsInputElement(JSObject htmlElement); - [JSImport("InputHelper.focusElement", "avalonia.ts")] + [JSImport("InputHelper.focusElement", "avalonia")] public static partial void FocusElement(JSObject htmlElement); - [JSImport("InputHelper.setCursor", "avalonia.ts")] + [JSImport("InputHelper.setCursor", "avalonia")] public static partial void SetCursor(JSObject htmlElement, string kind); - [JSImport("InputHelper.hide", "avalonia.ts")] + [JSImport("InputHelper.hide", "avalonia")] public static partial void HideElement(JSObject htmlElement); - [JSImport("InputHelper.show", "avalonia.ts")] + [JSImport("InputHelper.show", "avalonia")] public static partial void ShowElement(JSObject htmlElement); - [JSImport("InputHelper.setSurroundingText", "avalonia.ts")] + [JSImport("InputHelper.setSurroundingText", "avalonia")] public static partial void SetSurroundingText(JSObject htmlElement, string text, int start, int end); - [JSImport("InputHelper.setBounds", "avalonia.ts")] + [JSImport("InputHelper.setBounds", "avalonia")] public static partial void SetBounds(JSObject htmlElement, int x, int y, int width, int height, int caret); [JSImport("globalThis.navigator.clipboard.readText")] diff --git a/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs b/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs index 5cc86bf622..8144f64fc7 100644 --- a/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs +++ b/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs @@ -5,24 +5,24 @@ namespace Avalonia.Web.Interop; internal static partial class NativeControlHostHelper { - [JSImport("NativeControlHost.createDefaultChild", "avalonia.ts")] + [JSImport("NativeControlHost.createDefaultChild", "avalonia")] internal static partial JSObject CreateDefaultChild(JSObject? parent); - [JSImport("NativeControlHost.createAttachment", "avalonia.ts")] + [JSImport("NativeControlHost.createAttachment", "avalonia")] internal static partial JSObject CreateAttachment(); - [JSImport("NativeControlHost.initializeWithChildHandle", "avalonia.ts")] + [JSImport("NativeControlHost.initializeWithChildHandle", "avalonia")] internal static partial void InitializeWithChildHandle(JSObject element, JSObject child); - [JSImport("NativeControlHost.attachTo", "avalonia.ts")] + [JSImport("NativeControlHost.attachTo", "avalonia")] internal static partial void AttachTo(JSObject element, JSObject? host); - [JSImport("NativeControlHost.showInBounds", "avalonia.ts")] + [JSImport("NativeControlHost.showInBounds", "avalonia")] internal static partial void ShowInBounds(JSObject element, double x, double y, double width, double height); - [JSImport("NativeControlHost.hideWithSize", "avalonia.ts")] + [JSImport("NativeControlHost.hideWithSize", "avalonia")] internal static partial void HideWithSize(JSObject element, double width, double height); - [JSImport("NativeControlHost.releaseChild", "avalonia.ts")] + [JSImport("NativeControlHost.releaseChild", "avalonia")] internal static partial void ReleaseChild(JSObject element); } diff --git a/src/Web/Avalonia.Web/Interop/StorageHelper.cs b/src/Web/Avalonia.Web/Interop/StorageHelper.cs index 4dc55fd959..d770e852d9 100644 --- a/src/Web/Avalonia.Web/Interop/StorageHelper.cs +++ b/src/Web/Avalonia.Web/Interop/StorageHelper.cs @@ -5,51 +5,51 @@ namespace Avalonia.Web.Interop; internal static partial class StorageHelper { - [JSImport("Caniuse.canShowOpenFilePicker", "avalonia.ts")] + [JSImport("Caniuse.canShowOpenFilePicker", "avalonia")] public static partial bool CanShowOpenFilePicker(); - [JSImport("Caniuse.canShowSaveFilePicker", "avalonia.ts")] + [JSImport("Caniuse.canShowSaveFilePicker", "avalonia")] public static partial bool CanShowSaveFilePicker(); - [JSImport("Caniuse.canShowDirectoryPicker", "avalonia.ts")] + [JSImport("Caniuse.canShowDirectoryPicker", "avalonia")] public static partial bool CanShowDirectoryPicker(); - [JSImport("StorageProvider.selectFolderDialog", "storage.ts")] + [JSImport("StorageProvider.selectFolderDialog", "storage")] public static partial Task SelectFolderDialog(JSObject? startIn); - [JSImport("StorageProvider.openFileDialog", "storage.ts")] + [JSImport("StorageProvider.openFileDialog", "storage")] public static partial Task OpenFileDialog(JSObject? startIn, bool multiple, [JSMarshalAs>] object[]? types, bool excludeAcceptAllOption); - [JSImport("StorageProvider.saveFileDialog", "storage.ts")] + [JSImport("StorageProvider.saveFileDialog", "storage")] public static partial Task SaveFileDialog(JSObject? startIn, string? suggestedName, [JSMarshalAs>] object[]? types, bool excludeAcceptAllOption); - [JSImport("StorageProvider.openBookmark", "storage.ts")] + [JSImport("StorageProvider.openBookmark", "storage")] public static partial Task OpenBookmark(string key); - [JSImport("StorageItem.saveBookmark", "storage.ts")] + [JSImport("StorageItem.saveBookmark", "storage")] public static partial Task SaveBookmark(JSObject item); - [JSImport("StorageItem.deleteBookmark", "storage.ts")] + [JSImport("StorageItem.deleteBookmark", "storage")] public static partial Task DeleteBookmark(JSObject item); - [JSImport("StorageItem.getProperties", "storage.ts")] + [JSImport("StorageItem.getProperties", "storage")] public static partial Task GetProperties(JSObject item); - [JSImport("StorageItem.openWrite", "storage.ts")] + [JSImport("StorageItem.openWrite", "storage")] public static partial Task OpenWrite(JSObject item); - [JSImport("StorageItem.openRead", "storage.ts")] + [JSImport("StorageItem.openRead", "storage")] public static partial Task OpenRead(JSObject item); - [JSImport("StorageItem.getItems", "storage.ts")] + [JSImport("StorageItem.getItems", "storage")] [return: JSMarshalAs>] public static partial Task GetItems(JSObject item); - [JSImport("StorageItems.itemsArray", "storage.ts")] + [JSImport("StorageItems.itemsArray", "storage")] public static partial JSObject[] ItemsArray(JSObject item); - [JSImport("StorageProvider.createAcceptType", "storage.ts")] + [JSImport("StorageProvider.createAcceptType", "storage")] public static partial JSObject CreateAcceptType(string description, string[] mimeTypes); } diff --git a/src/Web/Avalonia.Web/Interop/StreamHelper.cs b/src/Web/Avalonia.Web/Interop/StreamHelper.cs index 0a83bbb871..9cd5ca2591 100644 --- a/src/Web/Avalonia.Web/Interop/StreamHelper.cs +++ b/src/Web/Avalonia.Web/Interop/StreamHelper.cs @@ -9,26 +9,26 @@ namespace Avalonia.Web.Storage; /// internal static partial class StreamHelper { - [JSImport("StreamHelper.seek", "avalonia.ts")] + [JSImport("StreamHelper.seek", "avalonia")] public static partial void Seek(JSObject stream, [JSMarshalAs] long position); - [JSImport("StreamHelper.truncate", "avalonia.ts")] + [JSImport("StreamHelper.truncate", "avalonia")] public static partial void Truncate(JSObject stream, [JSMarshalAs] long size); - [JSImport("StreamHelper.write", "avalonia.ts")] + [JSImport("StreamHelper.write", "avalonia")] public static partial Task WriteAsync(JSObject stream, [JSMarshalAs] ArraySegment data); - [JSImport("StreamHelper.close", "avalonia.ts")] + [JSImport("StreamHelper.close", "avalonia")] public static partial Task CloseAsync(JSObject stream); - [JSImport("StreamHelper.byteLength", "avalonia.ts")] + [JSImport("StreamHelper.byteLength", "avalonia")] [return: JSMarshalAs] public static partial long ByteLength(JSObject stream); - [JSImport("StreamHelper.sliceArrayBuffer", "avalonia.ts")] + [JSImport("StreamHelper.sliceArrayBuffer", "avalonia")] private static partial Task SliceToArrayBuffer(JSObject stream, [JSMarshalAs] long offset, int count); - [JSImport("StreamHelper.toMemoryView", "avalonia.ts")] + [JSImport("StreamHelper.toMemoryView", "avalonia")] [return: JSMarshalAs>] private static partial byte[] ArrayBufferToMemoryView(JSObject stream); diff --git a/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs b/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs index 774848e19f..3309a6dd9f 100644 --- a/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs +++ b/src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs @@ -4,7 +4,7 @@ using Avalonia.Rendering; namespace Avalonia.Web { - public class ManualTriggerRenderTimer : IRenderTimer + internal class ManualTriggerRenderTimer : IRenderTimer { private static readonly Stopwatch s_sw = Stopwatch.StartNew(); diff --git a/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs b/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs index 55ae2bdef0..81a621747e 100644 --- a/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs +++ b/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs @@ -20,7 +20,7 @@ internal class BrowserStorageProvider : IStorageProvider internal const string PickerCancelMessage = "The user aborted a request"; internal const string NoPermissionsMessage = "Permissions denied"; - private readonly Lazy> _lazyModule = new(() => JSHost.ImportAsync("storage.ts", "./storage.js")); + private readonly Lazy> _lazyModule = new(() => JSHost.ImportAsync("storage", "./storage.js")); public bool CanOpen => StorageHelper.CanShowOpenFilePicker(); public bool CanSave => StorageHelper.CanShowSaveFilePicker(); diff --git a/src/Web/Avalonia.Web/WindowingPlatform.cs b/src/Web/Avalonia.Web/WindowingPlatform.cs index 3f14680241..7c2a84516b 100644 --- a/src/Web/Avalonia.Web/WindowingPlatform.cs +++ b/src/Web/Avalonia.Web/WindowingPlatform.cs @@ -8,7 +8,7 @@ using Avalonia.Threading; namespace Avalonia.Web { - public class BrowserWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface + internal class BrowserWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface { private bool _signaled; private static KeyboardDevice? s_keyboard; diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia.ts index 98c912f940..a78fd7ca87 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia.ts @@ -6,15 +6,19 @@ import { Caniuse } from "./avalonia/caniuse"; import { StreamHelper } from "./avalonia/stream"; import { NativeControlHost } from "./avalonia/nativeControlHost"; -export async function createAvaloniaRuntime(api: RuntimeAPI): Promise { - api.setModuleImports("avalonia.ts", { - Caniuse, - Canvas, - InputHelper, - SizeWatcher, - DpiWatcher, - AvaloniaDOM, - StreamHelper, - NativeControlHost - }); +async function registerAvaloniaModule(api: RuntimeAPI): Promise { + api.setModuleImports("avalonia", avaloniaModule); } + +export const avaloniaModule = { + Caniuse, + Canvas, + InputHelper, + SizeWatcher, + DpiWatcher, + AvaloniaDOM, + StreamHelper, + NativeControlHost, + + registerAvaloniaModule +}; diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts index 017ca58b8f..9ae9b3d2a8 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts @@ -211,7 +211,7 @@ export class SizeWatcher { static observer: ResizeObserver; static elements: Map; - public static observe(element: HTMLElement, elementId: string, callback: (width: number, height: number) => void): void { + public static observe(element: HTMLElement, elementId: string | undefined, callback: (width: number, height: number) => void): void { if (!element || !callback) { return; } @@ -223,7 +223,7 @@ export class SizeWatcher { callback }; - SizeWatcher.elements.set(elementId, element); + SizeWatcher.elements.set(elementId ?? element.id, element); SizeWatcher.observer.observe(element); SizeWatcher.invoke(element); From 897f7c97064d9a4ca7894df35ee245bf77bd0bb3 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 10 Oct 2022 05:10:49 -0400 Subject: [PATCH 026/164] Reuse module initialization and make it configurable + some fixes --- samples/ControlCatalog.Web/App.razor.cs | 5 +- src/Web/Avalonia.Web.Blazor/AvaloniaView.cs | 3 +- .../BlazorSingleViewLifetime.cs | 21 +++-- src/Web/Avalonia.Web/Avalonia.Web.csproj | 8 +- src/Web/Avalonia.Web/AvaloniaView.cs | 3 - .../Avalonia.Web/BrowserSingleViewLifetime.cs | 79 ++++++++++--------- .../Avalonia.Web/Interop/AvaloniaModule.cs | 22 ++++++ src/Web/Avalonia.Web/Interop/CanvasHelper.cs | 6 +- src/Web/Avalonia.Web/Interop/DomHelper.cs | 8 +- src/Web/Avalonia.Web/Interop/InputHelper.cs | 24 +++--- .../Interop/NativeControlHostHelper.cs | 14 ++-- src/Web/Avalonia.Web/Interop/StorageHelper.cs | 30 +++---- src/Web/Avalonia.Web/Interop/StreamHelper.cs | 16 ++-- .../Storage/BlobReadableStream.cs | 2 + .../Storage/BrowserStorageProvider.cs | 12 +-- .../Avalonia.Web/Storage/WriteableStream.cs | 2 + .../Avalonia.Web/webapp/modules/avalonia.ts | 14 +++- .../webapp/modules/avalonia/dom.ts | 5 ++ 18 files changed, 163 insertions(+), 111 deletions(-) create mode 100644 src/Web/Avalonia.Web/Interop/AvaloniaModule.cs diff --git a/samples/ControlCatalog.Web/App.razor.cs b/samples/ControlCatalog.Web/App.razor.cs index ca3d3604b1..09ab4c3b68 100644 --- a/samples/ControlCatalog.Web/App.razor.cs +++ b/samples/ControlCatalog.Web/App.razor.cs @@ -7,8 +7,9 @@ public partial class App { protected override void OnParametersSet() { - WebAppBuilder.Configure() - .With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering + AppBuilder.Configure() + .UseBlazor() + // .With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering .SetupWithSingleViewLifetime(); base.OnParametersSet(); diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs index ae294151a6..909e2dd441 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.cs @@ -24,6 +24,7 @@ public class AvaloniaView : ComponentBase { builder.OpenElement(0, "div"); builder.AddAttribute(1, "id", _containerId); + builder.AddAttribute(2, "style", "width:100vw;height:100vh"); builder.CloseElement(); } @@ -31,7 +32,7 @@ public class AvaloniaView : ComponentBase { if (OperatingSystem.IsBrowser()) { - _ = await JSHost.ImportAsync("avalonia", "/_content/Avalonia.Web.Blazor/avalonia.js"); + await Avalonia.Web.Interop.AvaloniaModule.ImportMain(); _browserView = new BrowserView(_containerId); if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime lifetime) diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs b/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs index 26b4b15863..f38779f834 100644 --- a/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs +++ b/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs @@ -1,8 +1,11 @@ -using Avalonia.Controls; +using System.Runtime.Versioning; + +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; namespace Avalonia.Web.Blazor; +[SupportedOSPlatform("browser")] public static class WebAppBuilder { public static T SetupWithSingleViewLifetime( @@ -12,13 +15,21 @@ public static class WebAppBuilder return builder.SetupWithLifetime(new BlazorSingleViewLifetime()); } + public static T UseBlazor(this T builder) where T : AppBuilderBase, new() + { + return builder + .UseBrowser() + .With(new BrowserPlatformOptions + { + FrameworkAssetPathResolver = new(filePath => $"/_content/Avalonia.Web.Blazor/{filePath}") + }); + } + public static AppBuilder Configure() where TApp : Application, new() { - var builder = AppBuilder.Configure() - .UseBrowser(); - - return builder; + return AppBuilder.Configure() + .UseBlazor(); } internal class BlazorSingleViewLifetime : ISingleViewApplicationLifetime diff --git a/src/Web/Avalonia.Web/Avalonia.Web.csproj b/src/Web/Avalonia.Web/Avalonia.Web.csproj index a4756a5e2b..cdfa095865 100644 --- a/src/Web/Avalonia.Web/Avalonia.Web.csproj +++ b/src/Web/Avalonia.Web/Avalonia.Web.csproj @@ -39,10 +39,6 @@ - - - - @@ -52,4 +48,8 @@ + + + + diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index a5da719912..3a31679424 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -37,7 +37,6 @@ namespace Avalonia.Web private bool _useGL; private ITextInputMethodClient? _client; - private static int _canvasCount; public AvaloniaView(string divId) : this(DomHelper.GetElementById(divId) ?? throw new Exception($"Element with id {divId} was not found in the html document.")) @@ -63,8 +62,6 @@ namespace Avalonia.Web _splash = DomHelper.GetElementById("avalonia-splash"); - _canvas.SetProperty("id", $"avaloniaCanvas{_canvasCount++}"); - _topLevelImpl = new BrowserTopLevelImpl(this); _topLevel = new WebEmbeddableControlRoot(_topLevelImpl, () => diff --git a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs b/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs index d962956567..00ed961fbe 100644 --- a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs +++ b/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs @@ -1,51 +1,54 @@ -using System.Runtime.InteropServices.JavaScript; -using System; +using System; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Media; using Avalonia.Web.Skia; using System.Runtime.Versioning; -namespace Avalonia.Web +namespace Avalonia.Web; + +[SupportedOSPlatform("browser")] +public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime +{ + public AvaloniaView? View; + + public Control? MainView + { + get => View!.Content; + set => View!.Content = value; + } +} + +public class BrowserPlatformOptions +{ + public Func FrameworkAssetPathResolver { get; set; } = new(fileName => $"./{fileName}"); +} + + +[SupportedOSPlatform("browser")] +public static class WebAppBuilder { - [SupportedOSPlatform("browser")] - public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime + public static T SetupBrowserApp( + this T builder, string mainDivId) + where T : AppBuilderBase, new() { - public AvaloniaView? View; + var lifetime = new BrowserSingleViewLifetime(); - public Control? MainView - { - get => View!.Content; - set => View!.Content = value; - } + return builder + .UseBrowser() + .AfterSetup(b => + { + lifetime.View = new AvaloniaView(mainDivId); + }) + .SetupWithLifetime(lifetime); } - [SupportedOSPlatform("browser")] - public static partial class WebAppBuilder + public static T UseBrowser( + this T builder) + where T : AppBuilderBase, new() { - public static T SetupBrowserApp( - this T builder, string mainDivId) - where T : AppBuilderBase, new() - { - var lifetime = new BrowserSingleViewLifetime(); - - return builder - .UseBrowser() - .AfterSetup(b => - { - lifetime.View = new AvaloniaView(mainDivId); - }) - .SetupWithLifetime(lifetime); - } - - public static T UseBrowser( - this T builder) - where T : AppBuilderBase, new() - { - return builder - .UseWindowingSubsystem(BrowserWindowingPlatform.Register) - .UseSkia() - .With(new SkiaOptions { CustomGpuFactory = () => new BrowserSkiaGpu() }); - } + return builder + .UseWindowingSubsystem(BrowserWindowingPlatform.Register) + .UseSkia() + .With(new SkiaOptions { CustomGpuFactory = () => new BrowserSkiaGpu() }); } } diff --git a/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs b/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs new file mode 100644 index 0000000000..176b8d60fc --- /dev/null +++ b/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices.JavaScript; +using System.Threading.Tasks; + +namespace Avalonia.Web.Interop; + +internal static class AvaloniaModule +{ + public const string MainModuleName = "avalonia"; + public const string StorageModuleName = "storage"; + + public static Task ImportMain() + { + var options = AvaloniaLocator.Current.GetService() ?? new BrowserPlatformOptions(); + return JSHost.ImportAsync(MainModuleName, options.FrameworkAssetPathResolver("avalonia.js")); + } + + public static Task ImportStorage() + { + var options = AvaloniaLocator.Current.GetService() ?? new BrowserPlatformOptions(); + return JSHost.ImportAsync(StorageModuleName, options.FrameworkAssetPathResolver("storage.js")); + } +} diff --git a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs index ff07d1757b..efa94916fa 100644 --- a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs +++ b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs @@ -29,13 +29,13 @@ internal static partial class CanvasHelper return glInfo; } - [JSImport("Canvas.requestAnimationFrame", "avalonia")] + [JSImport("Canvas.requestAnimationFrame", AvaloniaModule.MainModuleName)] public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop); - [JSImport("Canvas.setCanvasSize", "avalonia")] + [JSImport("Canvas.setCanvasSize", AvaloniaModule.MainModuleName)] public static partial void SetCanvasSize(JSObject canvas, int height, int width); - [JSImport("Canvas.initGL", "avalonia")] + [JSImport("Canvas.initGL", AvaloniaModule.MainModuleName)] private static partial JSObject InitGL( JSObject canvas, string canvasId, diff --git a/src/Web/Avalonia.Web/Interop/DomHelper.cs b/src/Web/Avalonia.Web/Interop/DomHelper.cs index c2cf0a0c44..80f146a57a 100644 --- a/src/Web/Avalonia.Web/Interop/DomHelper.cs +++ b/src/Web/Avalonia.Web/Interop/DomHelper.cs @@ -8,20 +8,20 @@ internal static partial class DomHelper [JSImport("globalThis.document.getElementById")] internal static partial JSObject? GetElementById(string id); - [JSImport("AvaloniaDOM.createAvaloniaHost", "avalonia")] + [JSImport("AvaloniaDOM.createAvaloniaHost", AvaloniaModule.MainModuleName)] public static partial JSObject CreateAvaloniaHost(JSObject element); - [JSImport("AvaloniaDOM.addClass", "avalonia")] + [JSImport("AvaloniaDOM.addClass", AvaloniaModule.MainModuleName)] public static partial void AddCssClass(JSObject element, string className); - [JSImport("SizeWatcher.observe", "avalonia")] + [JSImport("SizeWatcher.observe", AvaloniaModule.MainModuleName)] public static partial JSObject ObserveSize( JSObject canvas, string? canvasId, [JSMarshalAs>] Action onSizeChanged); - [JSImport("DpiWatcher.start", "avalonia")] + [JSImport("DpiWatcher.start", AvaloniaModule.MainModuleName)] public static partial double ObserveDpi( [JSMarshalAs>] Action onDpiChanged); diff --git a/src/Web/Avalonia.Web/Interop/InputHelper.cs b/src/Web/Avalonia.Web/Interop/InputHelper.cs index bdd1957e03..cfec9f30dc 100644 --- a/src/Web/Avalonia.Web/Interop/InputHelper.cs +++ b/src/Web/Avalonia.Web/Interop/InputHelper.cs @@ -6,7 +6,7 @@ namespace Avalonia.Web.Interop; internal static partial class InputHelper { - [JSImport("InputHelper.subscribeKeyEvents", "avalonia")] + [JSImport("InputHelper.subscribeKeyEvents", AvaloniaModule.MainModuleName)] public static partial void SubscribeKeyEvents( JSObject htmlElement, [JSMarshalAs>] @@ -14,7 +14,7 @@ internal static partial class InputHelper [JSMarshalAs>] Func keyUp); - [JSImport("InputHelper.subscribeTextEvents", "avalonia")] + [JSImport("InputHelper.subscribeTextEvents", AvaloniaModule.MainModuleName)] public static partial void SubscribeTextEvents( JSObject htmlElement, [JSMarshalAs>] @@ -26,7 +26,7 @@ internal static partial class InputHelper [JSMarshalAs>] Func onCompositionEnd); - [JSImport("InputHelper.subscribePointerEvents", "avalonia")] + [JSImport("InputHelper.subscribePointerEvents", AvaloniaModule.MainModuleName)] public static partial void SubscribePointerEvents( JSObject htmlElement, [JSMarshalAs>] @@ -39,35 +39,35 @@ internal static partial class InputHelper Func wheel); - [JSImport("InputHelper.subscribeInputEvents", "avalonia")] + [JSImport("InputHelper.subscribeInputEvents", AvaloniaModule.MainModuleName)] public static partial void SubscribeInputEvents( JSObject htmlElement, [JSMarshalAs>] Func input); - [JSImport("InputHelper.clearInput", "avalonia")] + [JSImport("InputHelper.clearInput", AvaloniaModule.MainModuleName)] public static partial void ClearInputElement(JSObject htmlElement); - [JSImport("InputHelper.isInputElement", "avalonia")] + [JSImport("InputHelper.isInputElement", AvaloniaModule.MainModuleName)] public static partial void IsInputElement(JSObject htmlElement); - [JSImport("InputHelper.focusElement", "avalonia")] + [JSImport("InputHelper.focusElement", AvaloniaModule.MainModuleName)] public static partial void FocusElement(JSObject htmlElement); - [JSImport("InputHelper.setCursor", "avalonia")] + [JSImport("InputHelper.setCursor", AvaloniaModule.MainModuleName)] public static partial void SetCursor(JSObject htmlElement, string kind); - [JSImport("InputHelper.hide", "avalonia")] + [JSImport("InputHelper.hide", AvaloniaModule.MainModuleName)] public static partial void HideElement(JSObject htmlElement); - [JSImport("InputHelper.show", "avalonia")] + [JSImport("InputHelper.show", AvaloniaModule.MainModuleName)] public static partial void ShowElement(JSObject htmlElement); - [JSImport("InputHelper.setSurroundingText", "avalonia")] + [JSImport("InputHelper.setSurroundingText", AvaloniaModule.MainModuleName)] public static partial void SetSurroundingText(JSObject htmlElement, string text, int start, int end); - [JSImport("InputHelper.setBounds", "avalonia")] + [JSImport("InputHelper.setBounds", AvaloniaModule.MainModuleName)] public static partial void SetBounds(JSObject htmlElement, int x, int y, int width, int height, int caret); [JSImport("globalThis.navigator.clipboard.readText")] diff --git a/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs b/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs index 8144f64fc7..d3baaa2533 100644 --- a/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs +++ b/src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs @@ -5,24 +5,24 @@ namespace Avalonia.Web.Interop; internal static partial class NativeControlHostHelper { - [JSImport("NativeControlHost.createDefaultChild", "avalonia")] + [JSImport("NativeControlHost.createDefaultChild", AvaloniaModule.MainModuleName)] internal static partial JSObject CreateDefaultChild(JSObject? parent); - [JSImport("NativeControlHost.createAttachment", "avalonia")] + [JSImport("NativeControlHost.createAttachment", AvaloniaModule.MainModuleName)] internal static partial JSObject CreateAttachment(); - [JSImport("NativeControlHost.initializeWithChildHandle", "avalonia")] + [JSImport("NativeControlHost.initializeWithChildHandle", AvaloniaModule.MainModuleName)] internal static partial void InitializeWithChildHandle(JSObject element, JSObject child); - [JSImport("NativeControlHost.attachTo", "avalonia")] + [JSImport("NativeControlHost.attachTo", AvaloniaModule.MainModuleName)] internal static partial void AttachTo(JSObject element, JSObject? host); - [JSImport("NativeControlHost.showInBounds", "avalonia")] + [JSImport("NativeControlHost.showInBounds", AvaloniaModule.MainModuleName)] internal static partial void ShowInBounds(JSObject element, double x, double y, double width, double height); - [JSImport("NativeControlHost.hideWithSize", "avalonia")] + [JSImport("NativeControlHost.hideWithSize", AvaloniaModule.MainModuleName)] internal static partial void HideWithSize(JSObject element, double width, double height); - [JSImport("NativeControlHost.releaseChild", "avalonia")] + [JSImport("NativeControlHost.releaseChild", AvaloniaModule.MainModuleName)] internal static partial void ReleaseChild(JSObject element); } diff --git a/src/Web/Avalonia.Web/Interop/StorageHelper.cs b/src/Web/Avalonia.Web/Interop/StorageHelper.cs index d770e852d9..9a6cfb9fc2 100644 --- a/src/Web/Avalonia.Web/Interop/StorageHelper.cs +++ b/src/Web/Avalonia.Web/Interop/StorageHelper.cs @@ -5,51 +5,51 @@ namespace Avalonia.Web.Interop; internal static partial class StorageHelper { - [JSImport("Caniuse.canShowOpenFilePicker", "avalonia")] + [JSImport("Caniuse.canShowOpenFilePicker", AvaloniaModule.MainModuleName)] public static partial bool CanShowOpenFilePicker(); - [JSImport("Caniuse.canShowSaveFilePicker", "avalonia")] + [JSImport("Caniuse.canShowSaveFilePicker", AvaloniaModule.MainModuleName)] public static partial bool CanShowSaveFilePicker(); - [JSImport("Caniuse.canShowDirectoryPicker", "avalonia")] + [JSImport("Caniuse.canShowDirectoryPicker", AvaloniaModule.MainModuleName)] public static partial bool CanShowDirectoryPicker(); - [JSImport("StorageProvider.selectFolderDialog", "storage")] + [JSImport("StorageProvider.selectFolderDialog", AvaloniaModule.StorageModuleName)] public static partial Task SelectFolderDialog(JSObject? startIn); - [JSImport("StorageProvider.openFileDialog", "storage")] + [JSImport("StorageProvider.openFileDialog", AvaloniaModule.StorageModuleName)] public static partial Task OpenFileDialog(JSObject? startIn, bool multiple, [JSMarshalAs>] object[]? types, bool excludeAcceptAllOption); - [JSImport("StorageProvider.saveFileDialog", "storage")] + [JSImport("StorageProvider.saveFileDialog", AvaloniaModule.StorageModuleName)] public static partial Task SaveFileDialog(JSObject? startIn, string? suggestedName, [JSMarshalAs>] object[]? types, bool excludeAcceptAllOption); - [JSImport("StorageProvider.openBookmark", "storage")] + [JSImport("StorageProvider.openBookmark", AvaloniaModule.StorageModuleName)] public static partial Task OpenBookmark(string key); - [JSImport("StorageItem.saveBookmark", "storage")] + [JSImport("StorageItem.saveBookmark", AvaloniaModule.StorageModuleName)] public static partial Task SaveBookmark(JSObject item); - [JSImport("StorageItem.deleteBookmark", "storage")] + [JSImport("StorageItem.deleteBookmark", AvaloniaModule.StorageModuleName)] public static partial Task DeleteBookmark(JSObject item); - [JSImport("StorageItem.getProperties", "storage")] + [JSImport("StorageItem.getProperties", AvaloniaModule.StorageModuleName)] public static partial Task GetProperties(JSObject item); - [JSImport("StorageItem.openWrite", "storage")] + [JSImport("StorageItem.openWrite", AvaloniaModule.StorageModuleName)] public static partial Task OpenWrite(JSObject item); - [JSImport("StorageItem.openRead", "storage")] + [JSImport("StorageItem.openRead", AvaloniaModule.StorageModuleName)] public static partial Task OpenRead(JSObject item); - [JSImport("StorageItem.getItems", "storage")] + [JSImport("StorageItem.getItems", AvaloniaModule.StorageModuleName)] [return: JSMarshalAs>] public static partial Task GetItems(JSObject item); - [JSImport("StorageItems.itemsArray", "storage")] + [JSImport("StorageItems.itemsArray", AvaloniaModule.StorageModuleName)] public static partial JSObject[] ItemsArray(JSObject item); - [JSImport("StorageProvider.createAcceptType", "storage")] + [JSImport("StorageProvider.createAcceptType", AvaloniaModule.StorageModuleName)] public static partial JSObject CreateAcceptType(string description, string[] mimeTypes); } diff --git a/src/Web/Avalonia.Web/Interop/StreamHelper.cs b/src/Web/Avalonia.Web/Interop/StreamHelper.cs index 9cd5ca2591..d9de7bcbd8 100644 --- a/src/Web/Avalonia.Web/Interop/StreamHelper.cs +++ b/src/Web/Avalonia.Web/Interop/StreamHelper.cs @@ -2,33 +2,33 @@ using System.Runtime.InteropServices.JavaScript; using System.Threading.Tasks; -namespace Avalonia.Web.Storage; +namespace Avalonia.Web.Interop; /// /// Set of FileSystemWritableFileStream and Blob methods. /// internal static partial class StreamHelper { - [JSImport("StreamHelper.seek", "avalonia")] + [JSImport("StreamHelper.seek", AvaloniaModule.MainModuleName)] public static partial void Seek(JSObject stream, [JSMarshalAs] long position); - [JSImport("StreamHelper.truncate", "avalonia")] + [JSImport("StreamHelper.truncate", AvaloniaModule.MainModuleName)] public static partial void Truncate(JSObject stream, [JSMarshalAs] long size); - [JSImport("StreamHelper.write", "avalonia")] + [JSImport("StreamHelper.write", AvaloniaModule.MainModuleName)] public static partial Task WriteAsync(JSObject stream, [JSMarshalAs] ArraySegment data); - [JSImport("StreamHelper.close", "avalonia")] + [JSImport("StreamHelper.close", AvaloniaModule.MainModuleName)] public static partial Task CloseAsync(JSObject stream); - [JSImport("StreamHelper.byteLength", "avalonia")] + [JSImport("StreamHelper.byteLength", AvaloniaModule.MainModuleName)] [return: JSMarshalAs] public static partial long ByteLength(JSObject stream); - [JSImport("StreamHelper.sliceArrayBuffer", "avalonia")] + [JSImport("StreamHelper.sliceArrayBuffer", AvaloniaModule.MainModuleName)] private static partial Task SliceToArrayBuffer(JSObject stream, [JSMarshalAs] long offset, int count); - [JSImport("StreamHelper.toMemoryView", "avalonia")] + [JSImport("StreamHelper.toMemoryView", AvaloniaModule.MainModuleName)] [return: JSMarshalAs>] private static partial byte[] ArrayBufferToMemoryView(JSObject stream); diff --git a/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs b/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs index 640c2fd745..77734ea62f 100644 --- a/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs +++ b/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs @@ -4,6 +4,8 @@ using System.Runtime.InteropServices.JavaScript; using System.Threading; using System.Threading.Tasks; +using Avalonia.Web.Interop; + namespace Avalonia.Web.Storage; [System.Runtime.Versioning.SupportedOSPlatform("browser")] diff --git a/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs b/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs index 81a621747e..3932b79ad0 100644 --- a/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs +++ b/src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs @@ -20,7 +20,7 @@ internal class BrowserStorageProvider : IStorageProvider internal const string PickerCancelMessage = "The user aborted a request"; internal const string NoPermissionsMessage = "Permissions denied"; - private readonly Lazy> _lazyModule = new(() => JSHost.ImportAsync("storage", "./storage.js")); + private readonly Lazy _lazyModule = new(() => AvaloniaModule.ImportStorage()); public bool CanOpen => StorageHelper.CanShowOpenFilePicker(); public bool CanSave => StorageHelper.CanShowSaveFilePicker(); @@ -28,7 +28,7 @@ internal class BrowserStorageProvider : IStorageProvider public async Task> OpenFilePickerAsync(FilePickerOpenOptions options) { - _ = await _lazyModule.Value; + await _lazyModule.Value; var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; var (types, exludeAll) = ConvertFileTypes(options.FileTypeFilter); @@ -62,7 +62,7 @@ internal class BrowserStorageProvider : IStorageProvider public async Task SaveFilePickerAsync(FilePickerSaveOptions options) { - _ = await _lazyModule.Value; + await _lazyModule.Value; var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; var (types, exludeAll) = ConvertFileTypes(options.FileTypeChoices); @@ -90,7 +90,7 @@ internal class BrowserStorageProvider : IStorageProvider public async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) { - _ = await _lazyModule.Value; + await _lazyModule.Value; var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; try @@ -106,14 +106,14 @@ internal class BrowserStorageProvider : IStorageProvider public async Task OpenFileBookmarkAsync(string bookmark) { - _ = await _lazyModule.Value; + await _lazyModule.Value; var item = await StorageHelper.OpenBookmark(bookmark); return item is not null ? new JSStorageFile(item) : null; } public async Task OpenFolderBookmarkAsync(string bookmark) { - _ = await _lazyModule.Value; + await _lazyModule.Value; var item = await StorageHelper.OpenBookmark(bookmark); return item is not null ? new JSStorageFolder(item) : null; } diff --git a/src/Web/Avalonia.Web/Storage/WriteableStream.cs b/src/Web/Avalonia.Web/Storage/WriteableStream.cs index f9699fef7a..09e438c34e 100644 --- a/src/Web/Avalonia.Web/Storage/WriteableStream.cs +++ b/src/Web/Avalonia.Web/Storage/WriteableStream.cs @@ -4,6 +4,8 @@ using System.Runtime.InteropServices.JavaScript; using System.Threading; using System.Threading.Tasks; +using Avalonia.Web.Interop; + namespace Avalonia.Web.Storage; [System.Runtime.Versioning.SupportedOSPlatform("browser")] diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia.ts index a78fd7ca87..0642bd475d 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia.ts @@ -7,10 +7,18 @@ import { StreamHelper } from "./avalonia/stream"; import { NativeControlHost } from "./avalonia/nativeControlHost"; async function registerAvaloniaModule(api: RuntimeAPI): Promise { - api.setModuleImports("avalonia", avaloniaModule); + api.setModuleImports("avalonia", { + Caniuse, + Canvas, + InputHelper, + SizeWatcher, + DpiWatcher, + AvaloniaDOM, + StreamHelper, + NativeControlHost + }); } - -export const avaloniaModule = { +export { Caniuse, Canvas, InputHelper, diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts index 943b8330d5..494fde23e2 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts @@ -4,6 +4,8 @@ export class AvaloniaDOM { } static createAvaloniaHost(host: HTMLElement) { + const randomIdPart = Math.random().toString(36).replace(/[^a-z]+/g, "").substr(2, 10); + // Root element host.classList.add("avalonia-container"); host.tabIndex = 0; @@ -11,6 +13,7 @@ export class AvaloniaDOM { // Rendering target canvas const canvas = document.createElement("canvas"); + canvas.id = `canvas${randomIdPart}`; canvas.classList.add("avalonia-canvas"); canvas.style.backgroundColor = "#ccc"; canvas.style.width = "100%"; @@ -19,6 +22,7 @@ export class AvaloniaDOM { // Native controls host const nativeHost = document.createElement("div"); + canvas.id = `nativeHost${randomIdPart}`; nativeHost.classList.add("avalonia-native-host"); nativeHost.style.left = "0px"; nativeHost.style.top = "0px"; @@ -28,6 +32,7 @@ export class AvaloniaDOM { // IME const inputElement = document.createElement("input"); + canvas.id = `input${randomIdPart}`; inputElement.classList.add("avalonia-input-element"); inputElement.autocapitalize = "none"; inputElement.type = "text"; From 7c8982ae3f7db992f8482010e7c87b0974f75d44 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 10 Oct 2022 11:55:56 +0000 Subject: [PATCH 027/164] 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 75b3abbdd86720e2e688687283b49ef95e2ca63b Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 10 Oct 2022 18:25:44 +0200 Subject: [PATCH 028/164] Properly arrange Embedded controls during ArrangeOverride --- .../Documents/IInlineHost.cs | 2 - src/Avalonia.Controls/Documents/InlineRun.cs | 42 +++++++++ .../Documents/InlineUIContainer.cs | 48 +--------- src/Avalonia.Controls/RichTextBlock.cs | 87 ++++++++++++------- .../RichTextBlockTests.cs | 36 ++++++++ 5 files changed, 137 insertions(+), 78 deletions(-) create mode 100644 src/Avalonia.Controls/Documents/InlineRun.cs diff --git a/src/Avalonia.Controls/Documents/IInlineHost.cs b/src/Avalonia.Controls/Documents/IInlineHost.cs index da72c207be..5d142952ab 100644 --- a/src/Avalonia.Controls/Documents/IInlineHost.cs +++ b/src/Avalonia.Controls/Documents/IInlineHost.cs @@ -4,8 +4,6 @@ namespace Avalonia.Controls.Documents { internal interface IInlineHost : ILogical { - void AddVisualChild(IControl child); - void Invalidate(); } } diff --git a/src/Avalonia.Controls/Documents/InlineRun.cs b/src/Avalonia.Controls/Documents/InlineRun.cs new file mode 100644 index 0000000000..68c61ca3fa --- /dev/null +++ b/src/Avalonia.Controls/Documents/InlineRun.cs @@ -0,0 +1,42 @@ +using Avalonia.Media; +using Avalonia.Media.TextFormatting; +using Avalonia.Utilities; + +namespace Avalonia.Controls.Documents +{ + internal class EmbeddedControlRun : DrawableTextRun + { + public EmbeddedControlRun(IControl control, TextRunProperties properties) + { + Control = control; + Properties = properties; + } + + public IControl Control { get; } + + public override TextRunProperties? Properties { get; } + + public override Size Size => Control.DesiredSize; + + public override double Baseline + { + get + { + double baseline = Size.Height; + double baselineOffsetValue = Control.GetValue(TextBlock.BaselineOffsetProperty); + + if (!MathUtilities.IsZero(baselineOffsetValue)) + { + baseline = baselineOffsetValue; + } + + return -baseline; + } + } + + public override void Draw(DrawingContext drawingContext, Point origin) + { + //noop + } + } +} diff --git a/src/Avalonia.Controls/Documents/InlineUIContainer.cs b/src/Avalonia.Controls/Documents/InlineUIContainer.cs index d632e5fea7..7107fb7fed 100644 --- a/src/Avalonia.Controls/Documents/InlineUIContainer.cs +++ b/src/Avalonia.Controls/Documents/InlineUIContainer.cs @@ -3,7 +3,6 @@ using System.Text; using Avalonia.Media; using Avalonia.Media.TextFormatting; using Avalonia.Metadata; -using Avalonia.Utilities; namespace Avalonia.Controls.Documents { @@ -59,56 +58,11 @@ namespace Avalonia.Controls.Documents internal override void BuildTextRun(IList textRuns) { - if(InlineHost == null) - { - return; - } - - ((ISetLogicalParent)Child).SetParent(InlineHost); - - InlineHost.AddVisualChild(Child); - - textRuns.Add(new InlineRun(Child, CreateTextRunProperties())); + textRuns.Add(new EmbeddedControlRun(Child, CreateTextRunProperties())); } internal override void AppendText(StringBuilder stringBuilder) { } - - private class InlineRun : DrawableTextRun - { - public InlineRun(IControl control, TextRunProperties properties) - { - Control = control; - Properties = properties; - } - - public IControl Control { get; } - - public override TextRunProperties? Properties { get; } - - public override Size Size => Control.DesiredSize; - - public override double Baseline - { - get - { - double baseline = Size.Height; - double baselineOffsetValue = Control.GetValue(TextBlock.BaselineOffsetProperty); - - if (!MathUtilities.IsZero(baselineOffsetValue)) - { - baseline = baselineOffsetValue; - } - - return -baseline; - } - } - - public override void Draw(DrawingContext drawingContext, Point origin) - { - //noop - } - } } } diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs index 906a038ec3..a7eee504ec 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/RichTextBlock.cs @@ -61,6 +61,7 @@ namespace Avalonia.Controls private int _selectionStart; private int _selectionEnd; private int _wordSelectionStart = -1; + private IReadOnlyList? _textRuns; static RichTextBlock() { @@ -277,8 +278,8 @@ namespace Avalonia.Controls protected override void SetText(string? text) { var oldValue = GetText(); - - AddText(text); + + AddText(text); RaisePropertyChanged(TextProperty, oldValue, text); } @@ -301,18 +302,9 @@ namespace Avalonia.Controls ITextSource textSource; - if (HasComplexContent) + if (_textRuns != null) { - var inlines = Inlines!; - - var textRuns = new List(); - - foreach (var inline in inlines) - { - inline.BuildTextRun(textRuns); - } - - textSource = new InlinesTextSource(textRuns); + textSource = new InlinesTextSource(_textRuns); } else { @@ -546,27 +538,72 @@ namespace Avalonia.Controls protected override Size MeasureOverride(Size availableSize) { - foreach (var child in VisualChildren) + LogicalChildren.Clear(); + + VisualChildren.Clear(); + + if (Inlines != null && Inlines.Count > 0) { - if (child is Control control) + var inlines = Inlines; + + var textRuns = new List(); + + foreach (var inline in inlines) { - control.Measure(Size.Infinity); + inline.BuildTextRun(textRuns); + } + + foreach (var textRun in textRuns) + { + if (textRun is EmbeddedControlRun controlRun && + controlRun.Control is Control control) + { + ((ISetLogicalParent)control).SetParent(this); + + VisualChildren.Add(control); + + control.Measure(Size.Infinity); + } } + + _textRuns = textRuns; + } + else + { + _textRuns = null; } - + return base.MeasureOverride(availableSize); } protected override Size ArrangeOverride(Size finalSize) { - foreach (var child in VisualChildren) + if (HasComplexContent) { - if (child is Control control) + var currentY = 0.0; + + foreach (var textLine in TextLayout.TextLines) { - control.Arrange(new Rect(control.DesiredSize)); + var currentX = textLine.Start; + + foreach (var run in textLine.TextRuns) + { + if (run is DrawableTextRun drawable) + { + if (drawable is EmbeddedControlRun controlRun + && controlRun.Control is Control control) + { + control.Arrange(new Rect(new Point(currentX, currentY), control.DesiredSize)); + } + + currentX += drawable.Size.Width; + } + } + + currentY += textLine.Height; } } - + return base.ArrangeOverride(finalSize); } @@ -618,14 +655,6 @@ namespace Avalonia.Controls } } - void IInlineHost.AddVisualChild(IControl child) - { - if (child.VisualParent == null) - { - VisualChildren.Add(child); - } - } - void IInlineHost.Invalidate() { InvalidateTextLayout(); diff --git a/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs index c74f13b808..05007e4f2e 100644 --- a/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs @@ -1,4 +1,6 @@ using Avalonia.Controls.Documents; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; using Avalonia.Media; using Avalonia.UnitTests; using Xunit; @@ -92,5 +94,39 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(target, run.Parent); } } + + [Fact] + public void InlineUIContainer_Child_Schould_Be_Arranged() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new RichTextBlock(); + + var button = new Button { Content = "12345678" }; + + button.Template = new FuncControlTemplate