From 14029cb934e7509e0b497de073fa680a1b4fc5a8 Mon Sep 17 00:00:00 2001 From: Arhell Date: Tue, 4 Oct 2022 01:05:27 +0300 Subject: [PATCH 01/53] 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 02/53] 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 0d05c40ca25381cec24a1c6456891113794d7b6a Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 10 Oct 2022 04:05:14 -0400 Subject: [PATCH 03/53] 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 04/53] 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 c682c469487ff0a46c525a228e991f463634e93c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 6 Oct 2022 18:25:17 +0100 Subject: [PATCH 05/53] better example splash screen. --- src/Web/Avalonia.Web.Sample/app.css | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Web/Avalonia.Web.Sample/app.css b/src/Web/Avalonia.Web.Sample/app.css index 04ea5bee19..27680f6e1a 100644 --- a/src/Web/Avalonia.Web.Sample/app.css +++ b/src/Web/Avalonia.Web.Sample/app.css @@ -4,12 +4,15 @@ } #avalonia-splash { - position: absolute; + position: relative; height: 100%; width: 100%; color: whitesmoke; background: #171C2C; font-family: 'Nunito', sans-serif; + background-position: center; + background-size: cover; + background-repeat: no-repeat; } #avalonia-splash a{ @@ -24,15 +27,23 @@ } .splash-close { - animation: fadeOut 1s forwards; + animation: slide 0.5s linear 1s forwards; } -@keyframes fadeOut { - from { - opacity: 1; +@keyframes slide { + 0% { + top: 0%; } - to { + 50% { + opacity: 80%; + } + + 100% { + top: 100%; + overflow: hidden; opacity: 0; + display: none; + visibility: collapse; } } From 4a94292d339f49e23a198a70accd846f20a7c6d2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 6 Oct 2022 21:42:56 +0100 Subject: [PATCH 06/53] keep input box at zIndex-1 to prevent it interfering with the user... still works luckily --- src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts index 494fde23e2..da499cd1ec 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts @@ -47,6 +47,7 @@ export class AvaloniaDOM { inputElement.style.color = "transparent"; inputElement.style.display = "none"; inputElement.style.height = "20px"; + inputElement.style.zIndex = "-1"; inputElement.onpaste = function () { return false; }; inputElement.oncopy = function () { return false; }; inputElement.oncut = function () { return false; }; From 5503e8f77593f2d1119fc1803eaa65f032e65a0b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 10 Oct 2022 18:28:14 +0100 Subject: [PATCH 07/53] move sample, and fix splash screen positioning and rename blazor sample. --- Avalonia.sln | 12 ++--- .../App.razor | 0 .../App.razor.cs | 2 +- .../ControlCatalog.Blazor.Web.csproj | 29 +++++++++++++ .../Pages/Index.razor | 0 samples/ControlCatalog.Blazor.Web/Program.cs | 29 +++++++++++++ .../Properties/launchSettings.json | 0 .../Shared/MainLayout.razor | 0 .../_Imports.razor | 0 .../wwwroot/css/app.css | 0 .../wwwroot/favicon.ico | Bin .../wwwroot/index.html | 0 .../ControlCatalog.Web.csproj | 40 +++++++++++------ .../EmbedSample.Browser.cs | 2 - .../ControlCatalog.Web}/Logo.svg | 0 samples/ControlCatalog.Web/Program.cs | 34 +++++---------- .../ControlCatalog.Web}/app.css | 0 .../ControlCatalog.Web}/embed.js | 0 .../ControlCatalog.Web}/favicon.ico | Bin .../ControlCatalog.Web}/index.html | 2 +- .../ControlCatalog.Web}/main.js | 0 .../runtimeconfig.template.json | 0 .../Avalonia.Web.Sample.csproj | 41 ------------------ src/Web/Avalonia.Web.Sample/Program.cs | 19 -------- .../webapp/modules/avalonia/dom.ts | 1 + 25 files changed, 105 insertions(+), 106 deletions(-) rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/App.razor (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/App.razor.cs (91%) create mode 100644 samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/Pages/Index.razor (100%) create mode 100644 samples/ControlCatalog.Blazor.Web/Program.cs rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/Properties/launchSettings.json (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/Shared/MainLayout.razor (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/_Imports.razor (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/wwwroot/css/app.css (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/wwwroot/favicon.ico (100%) rename samples/{ControlCatalog.Web => ControlCatalog.Blazor.Web}/wwwroot/index.html (100%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/EmbedSample.Browser.cs (98%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/Logo.svg (100%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/app.css (100%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/embed.js (100%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/favicon.ico (100%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/index.html (95%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/main.js (100%) rename {src/Web/Avalonia.Web.Sample => samples/ControlCatalog.Web}/runtimeconfig.template.json (100%) delete mode 100644 src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj delete mode 100644 src/Web/Avalonia.Web.Sample/Program.cs diff --git a/Avalonia.sln b/Avalonia.sln index 81a9b43890..d53ccd63fb 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -198,7 +198,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{86A3F706-DC3 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Blazor", "src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj", "{25831348-EB2A-483E-9576-E8F6528674A5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Web", "samples\ControlCatalog.Web\ControlCatalog.Web.csproj", "{C08E9894-AA92-426E-BF56-033E262CAD3E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Blazor.Web", "samples\ControlCatalog.Web\ControlCatalog.Blazor.Web.csproj", "{C08E9894-AA92-426E-BF56-033E262CAD3E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "samples\interop\WindowsInteropTest\WindowsInteropTest.csproj", "{26A98DA1-D89D-4A95-8152-349F404DA2E2}" EndProject @@ -218,13 +218,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web", "src\Web\Ava EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Sample", "src\Web\Avalonia.Web.Sample\Avalonia.Web.Sample.csproj", "{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.Android", "samples\MobileSandbox.Android\MobileSandbox.Android.csproj", "{C90FE60B-B01E-4F35-91D6-379D6966030F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Android", "samples\MobileSandbox.Android\MobileSandbox.Android.csproj", "{C90FE60B-B01E-4F35-91D6-379D6966030F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.iOS", "samples\MobileSandbox.iOS\MobileSandbox.iOS.csproj", "{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.iOS", "samples\MobileSandbox.iOS\MobileSandbox.iOS.csproj", "{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -579,6 +579,7 @@ Global {41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} + {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C} {11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098} @@ -595,7 +596,6 @@ Global {1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {76D39FF6-6B4F-46C4-93CD-E6FC4665739E} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} - {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {3B8519C1-2F51-4F12-A348-120AB91D4532} = {9B9E3891-2366-4253-A952-D08BCEB71098} {C90FE60B-B01E-4F35-91D6-379D6966030F} = {9B9E3891-2366-4253-A952-D08BCEB71098} {FED9A71D-00D7-4F40-A9E4-1229EEA28EEB} = {9B9E3891-2366-4253-A952-D08BCEB71098} diff --git a/samples/ControlCatalog.Web/App.razor b/samples/ControlCatalog.Blazor.Web/App.razor similarity index 100% rename from samples/ControlCatalog.Web/App.razor rename to samples/ControlCatalog.Blazor.Web/App.razor diff --git a/samples/ControlCatalog.Web/App.razor.cs b/samples/ControlCatalog.Blazor.Web/App.razor.cs similarity index 91% rename from samples/ControlCatalog.Web/App.razor.cs rename to samples/ControlCatalog.Blazor.Web/App.razor.cs index 09ab4c3b68..8cc0095f20 100644 --- a/samples/ControlCatalog.Web/App.razor.cs +++ b/samples/ControlCatalog.Blazor.Web/App.razor.cs @@ -1,7 +1,7 @@ using Avalonia; using Avalonia.Web.Blazor; -namespace ControlCatalog.Web; +namespace ControlCatalog.Blazor.Web; public partial class App { diff --git a/samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj b/samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj new file mode 100644 index 0000000000..03fb31f0d3 --- /dev/null +++ b/samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj @@ -0,0 +1,29 @@ + + + net7.0 + browser-wasm + enable + 16777216 + false + false + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog.Web/Pages/Index.razor b/samples/ControlCatalog.Blazor.Web/Pages/Index.razor similarity index 100% rename from samples/ControlCatalog.Web/Pages/Index.razor rename to samples/ControlCatalog.Blazor.Web/Pages/Index.razor diff --git a/samples/ControlCatalog.Blazor.Web/Program.cs b/samples/ControlCatalog.Blazor.Web/Program.cs new file mode 100644 index 0000000000..d71b125fa1 --- /dev/null +++ b/samples/ControlCatalog.Blazor.Web/Program.cs @@ -0,0 +1,29 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.DependencyInjection; +using ControlCatalog.Blazor.Web; + +public class Program +{ + public static async Task Main(string[] args) + { + await CreateHostBuilder(args).Build().RunAsync(); + } + + public static WebAssemblyHostBuilder CreateHostBuilder(string[] args) + { + var builder = WebAssemblyHostBuilder.CreateDefault(args); + + builder.RootComponents.Add("#app"); + + builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + + return builder; + } +} + + + + diff --git a/samples/ControlCatalog.Web/Properties/launchSettings.json b/samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json similarity index 100% rename from samples/ControlCatalog.Web/Properties/launchSettings.json rename to samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json diff --git a/samples/ControlCatalog.Web/Shared/MainLayout.razor b/samples/ControlCatalog.Blazor.Web/Shared/MainLayout.razor similarity index 100% rename from samples/ControlCatalog.Web/Shared/MainLayout.razor rename to samples/ControlCatalog.Blazor.Web/Shared/MainLayout.razor diff --git a/samples/ControlCatalog.Web/_Imports.razor b/samples/ControlCatalog.Blazor.Web/_Imports.razor similarity index 100% rename from samples/ControlCatalog.Web/_Imports.razor rename to samples/ControlCatalog.Blazor.Web/_Imports.razor diff --git a/samples/ControlCatalog.Web/wwwroot/css/app.css b/samples/ControlCatalog.Blazor.Web/wwwroot/css/app.css similarity index 100% rename from samples/ControlCatalog.Web/wwwroot/css/app.css rename to samples/ControlCatalog.Blazor.Web/wwwroot/css/app.css diff --git a/samples/ControlCatalog.Web/wwwroot/favicon.ico b/samples/ControlCatalog.Blazor.Web/wwwroot/favicon.ico similarity index 100% rename from samples/ControlCatalog.Web/wwwroot/favicon.ico rename to samples/ControlCatalog.Blazor.Web/wwwroot/favicon.ico diff --git a/samples/ControlCatalog.Web/wwwroot/index.html b/samples/ControlCatalog.Blazor.Web/wwwroot/index.html similarity index 100% rename from samples/ControlCatalog.Web/wwwroot/index.html rename to samples/ControlCatalog.Blazor.Web/wwwroot/index.html diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj index 03fb31f0d3..4dd4ca25d0 100644 --- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj +++ b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj @@ -1,29 +1,41 @@ - + net7.0 browser-wasm - enable - 16777216 - false - false + main.js + Exe + true + true + true + -sVERBOSE -sERROR_ON_UNDEFINED_SYMBOLS=0 - - - - + + true + true + full + true + true + true + -O3 + -O3 + - + - - + + + + + + + + - - diff --git a/src/Web/Avalonia.Web.Sample/EmbedSample.Browser.cs b/samples/ControlCatalog.Web/EmbedSample.Browser.cs similarity index 98% rename from src/Web/Avalonia.Web.Sample/EmbedSample.Browser.cs rename to samples/ControlCatalog.Web/EmbedSample.Browser.cs index 5baa4a6b35..5cfbb608cc 100644 --- a/src/Web/Avalonia.Web.Sample/EmbedSample.Browser.cs +++ b/samples/ControlCatalog.Web/EmbedSample.Browser.cs @@ -1,7 +1,5 @@ using System; using System.Runtime.InteropServices.JavaScript; - -using Avalonia; using Avalonia.Platform; using Avalonia.Web; diff --git a/src/Web/Avalonia.Web.Sample/Logo.svg b/samples/ControlCatalog.Web/Logo.svg similarity index 100% rename from src/Web/Avalonia.Web.Sample/Logo.svg rename to samples/ControlCatalog.Web/Logo.svg diff --git a/samples/ControlCatalog.Web/Program.cs b/samples/ControlCatalog.Web/Program.cs index d1a7925813..52acabb0fa 100644 --- a/samples/ControlCatalog.Web/Program.cs +++ b/samples/ControlCatalog.Web/Program.cs @@ -1,29 +1,19 @@ -using System; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.Extensions.DependencyInjection; +using Avalonia; +using Avalonia.Web; +using ControlCatalog; using ControlCatalog.Web; -public class Program +internal partial class Program { - public static async Task Main(string[] args) + private static void Main(string[] args) { - await CreateHostBuilder(args).Build().RunAsync(); + BuildAvaloniaApp() + .AfterSetup(_ => + { + ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb(); + }).SetupBrowserApp("out"); } - public static WebAssemblyHostBuilder CreateHostBuilder(string[] args) - { - var builder = WebAssemblyHostBuilder.CreateDefault(args); - - builder.RootComponents.Add("#app"); - - builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); - - return builder; - } + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure(); } - - - - diff --git a/src/Web/Avalonia.Web.Sample/app.css b/samples/ControlCatalog.Web/app.css similarity index 100% rename from src/Web/Avalonia.Web.Sample/app.css rename to samples/ControlCatalog.Web/app.css diff --git a/src/Web/Avalonia.Web.Sample/embed.js b/samples/ControlCatalog.Web/embed.js similarity index 100% rename from src/Web/Avalonia.Web.Sample/embed.js rename to samples/ControlCatalog.Web/embed.js diff --git a/src/Web/Avalonia.Web.Sample/favicon.ico b/samples/ControlCatalog.Web/favicon.ico similarity index 100% rename from src/Web/Avalonia.Web.Sample/favicon.ico rename to samples/ControlCatalog.Web/favicon.ico diff --git a/src/Web/Avalonia.Web.Sample/index.html b/samples/ControlCatalog.Web/index.html similarity index 95% rename from src/Web/Avalonia.Web.Sample/index.html rename to samples/ControlCatalog.Web/index.html index ee023790fb..226ae70695 100644 --- a/src/Web/Avalonia.Web.Sample/index.html +++ b/samples/ControlCatalog.Web/index.html @@ -4,7 +4,7 @@ - Avalonia.Web.Sample + AvaloniaUI - ControlCatalog diff --git a/src/Web/Avalonia.Web.Sample/main.js b/samples/ControlCatalog.Web/main.js similarity index 100% rename from src/Web/Avalonia.Web.Sample/main.js rename to samples/ControlCatalog.Web/main.js diff --git a/src/Web/Avalonia.Web.Sample/runtimeconfig.template.json b/samples/ControlCatalog.Web/runtimeconfig.template.json similarity index 100% rename from src/Web/Avalonia.Web.Sample/runtimeconfig.template.json rename to samples/ControlCatalog.Web/runtimeconfig.template.json diff --git a/src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj b/src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj deleted file mode 100644 index 13aad8c13e..0000000000 --- a/src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - net7.0 - browser-wasm - main.js - Exe - true - true - true - -sVERBOSE -sERROR_ON_UNDEFINED_SYMBOLS=0 - - - - true - true - full - true - true - true - -O3 - -O3 - - - - - - - - - - - - - - - - - - - - diff --git a/src/Web/Avalonia.Web.Sample/Program.cs b/src/Web/Avalonia.Web.Sample/Program.cs deleted file mode 100644 index 52acabb0fa..0000000000 --- a/src/Web/Avalonia.Web.Sample/Program.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Avalonia; -using Avalonia.Web; -using ControlCatalog; -using ControlCatalog.Web; - -internal partial class Program -{ - private static void Main(string[] args) - { - BuildAvaloniaApp() - .AfterSetup(_ => - { - ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb(); - }).SetupBrowserApp("out"); - } - - public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure(); -} diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts index da499cd1ec..2257d56a92 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts @@ -10,6 +10,7 @@ export class AvaloniaDOM { host.classList.add("avalonia-container"); host.tabIndex = 0; host.oncontextmenu = function () { return false; }; + host.style.overflow = "hidden"; // Rendering target canvas const canvas = document.createElement("canvas"); From f2e5193819b4dd661cfa3ae1d662fcfa2684c5a8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 10 Oct 2022 18:40:42 +0100 Subject: [PATCH 08/53] add roots.xml --- samples/ControlCatalog.Web/ControlCatalog.Web.csproj | 4 ++++ samples/ControlCatalog.Web/Roots.xml | 6 ++++++ 2 files changed, 10 insertions(+) create mode 100644 samples/ControlCatalog.Web/Roots.xml diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj index 4dd4ca25d0..6dc6a2ec3e 100644 --- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj +++ b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj @@ -21,6 +21,10 @@ -O3 + + + + diff --git a/samples/ControlCatalog.Web/Roots.xml b/samples/ControlCatalog.Web/Roots.xml new file mode 100644 index 0000000000..3c13098159 --- /dev/null +++ b/samples/ControlCatalog.Web/Roots.xml @@ -0,0 +1,6 @@ + + + + + + From 6cf47980f337b4e8f9c47c463fc60937de26345f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 11 Oct 2022 08:59:16 +0100 Subject: [PATCH 09/53] fix trimmer roots include. --- samples/ControlCatalog.Web/ControlCatalog.Web.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj index 6dc6a2ec3e..0ddec3444b 100644 --- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj +++ b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj @@ -21,9 +21,9 @@ -O3 - + - + From 33f9f52ed4eaf4df6fef0c9bc2036f2c1dfb9ffd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 11 Oct 2022 12:48:04 +0100 Subject: [PATCH 10/53] fix build error. --- samples/ControlCatalog.Blazor.Web/_Imports.razor | 1 - 1 file changed, 1 deletion(-) diff --git a/samples/ControlCatalog.Blazor.Web/_Imports.razor b/samples/ControlCatalog.Blazor.Web/_Imports.razor index 04c7a8690e..9088ea0731 100644 --- a/samples/ControlCatalog.Blazor.Web/_Imports.razor +++ b/samples/ControlCatalog.Blazor.Web/_Imports.razor @@ -6,6 +6,5 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.JSInterop -@using ControlCatalog.Web @using ControlCatalog.Web.Shared @using SkiaSharp From 70e5844df9c6009c8fa1bba0d81c3bdab9504afd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 11 Oct 2022 12:48:35 +0100 Subject: [PATCH 11/53] update sln. --- Avalonia.sln | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Avalonia.sln b/Avalonia.sln index d53ccd63fb..61592e1d44 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -198,8 +198,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{86A3F706-DC3 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Blazor", "src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj", "{25831348-EB2A-483E-9576-E8F6528674A5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Blazor.Web", "samples\ControlCatalog.Web\ControlCatalog.Blazor.Web.csproj", "{C08E9894-AA92-426E-BF56-033E262CAD3E}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "samples\interop\WindowsInteropTest\WindowsInteropTest.csproj", "{26A98DA1-D89D-4A95-8152-349F404DA2E2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlSamples", "samples\SampleControls\ControlSamples.csproj", "{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}" @@ -216,8 +214,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevGenerators", "src\tools\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web", "src\Web\Avalonia.Web\Avalonia.Web.csproj", "{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Sample", "src\Web\Avalonia.Web.Sample\Avalonia.Web.Sample.csproj", "{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Android", "samples\MobileSandbox.Android\MobileSandbox.Android.csproj", "{C90FE60B-B01E-4F35-91D6-379D6966030F}" @@ -226,6 +222,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.iOS", "sample EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Blazor.Web", "samples\ControlCatalog.Blazor.Web\ControlCatalog.Blazor.Web.csproj", "{6A710364-AE6D-40BD-968B-024311527AC2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Web", "samples\ControlCatalog.Web\ControlCatalog.Web.csproj", "{8B3E8405-DE18-4048-A459-9CA4AC3319A2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -480,10 +480,6 @@ Global {25831348-EB2A-483E-9576-E8F6528674A5}.Debug|Any CPU.Build.0 = Debug|Any CPU {25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.ActiveCfg = Release|Any CPU {25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.Build.0 = Release|Any CPU - {C08E9894-AA92-426E-BF56-033E262CAD3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C08E9894-AA92-426E-BF56-033E262CAD3E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C08E9894-AA92-426E-BF56-033E262CAD3E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C08E9894-AA92-426E-BF56-033E262CAD3E}.Release|Any CPU.Build.0 = Release|Any CPU {26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {26A98DA1-D89D-4A95-8152-349F404DA2E2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -516,10 +512,6 @@ Global {76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Debug|Any CPU.Build.0 = Debug|Any CPU {76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.ActiveCfg = Release|Any CPU {76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.Build.0 = Release|Any CPU - {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Release|Any CPU.Build.0 = Release|Any CPU {3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.Build.0 = Debug|Any CPU {3B8519C1-2F51-4F12-A348-120AB91D4532}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -536,6 +528,14 @@ Global {62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Debug|Any CPU.Build.0 = Debug|Any CPU {62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.ActiveCfg = Release|Any CPU {62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.Build.0 = Release|Any CPU + {6A710364-AE6D-40BD-968B-024311527AC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A710364-AE6D-40BD-968B-024311527AC2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A710364-AE6D-40BD-968B-024311527AC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A710364-AE6D-40BD-968B-024311527AC2}.Release|Any CPU.Build.0 = Release|Any CPU + {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -587,7 +587,6 @@ Global {676D6BFD-029D-4E43-BFC7-3892265CE251} = {9B9E3891-2366-4253-A952-D08BCEB71098} {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} - {C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098} {26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098} {70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098} @@ -595,11 +594,12 @@ Global {EABE2161-989B-42BF-BD8D-1E34B20C21F1} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {76D39FF6-6B4F-46C4-93CD-E6FC4665739E} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} - {1F61B6F1-B881-4E27-A5B0-09D1F543F7AC} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} {3B8519C1-2F51-4F12-A348-120AB91D4532} = {9B9E3891-2366-4253-A952-D08BCEB71098} {C90FE60B-B01E-4F35-91D6-379D6966030F} = {9B9E3891-2366-4253-A952-D08BCEB71098} {FED9A71D-00D7-4F40-A9E4-1229EEA28EEB} = {9B9E3891-2366-4253-A952-D08BCEB71098} {62D392C9-81CF-487F-92E8-598B2AF3FDCE} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {6A710364-AE6D-40BD-968B-024311527AC2} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {8B3E8405-DE18-4048-A459-9CA4AC3319A2} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} From ff25d17c0b70ea8c3fad120f5aa11036ecbd80c2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 11 Oct 2022 12:52:40 +0100 Subject: [PATCH 12/53] fix build --- samples/ControlCatalog.Blazor.Web/_Imports.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog.Blazor.Web/_Imports.razor b/samples/ControlCatalog.Blazor.Web/_Imports.razor index 9088ea0731..0e6d11b419 100644 --- a/samples/ControlCatalog.Blazor.Web/_Imports.razor +++ b/samples/ControlCatalog.Blazor.Web/_Imports.razor @@ -6,5 +6,5 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.JSInterop -@using ControlCatalog.Web.Shared +@using ControlCatalog.Blazor.Web.Shared @using SkiaSharp From ebdb4e1974eeef3900df4cc397217357dedf9571 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 11 Oct 2022 14:31:57 +0000 Subject: [PATCH 13/53] Seperate Avalonia lifecycle from android activity --- .../ControlCatalog.Android/MainActivity.cs | 12 +--- .../ControlCatalog.Android/SplashActivity.cs | 13 +++- .../Avalonia.Android/AvaloniaActivity.cs | 66 +++++-------------- .../AvaloniaSplashActivity.cs | 34 ++++++++++ src/Android/Avalonia.Android/AvaloniaView.cs | 2 +- .../OpenGL/GlPlatformSurface.cs | 6 +- .../Platform/SkiaPlatform/TopLevelImpl.cs | 7 -- .../Avalonia.Android/SingleViewLifetime.cs | 26 ++++++++ 8 files changed, 96 insertions(+), 70 deletions(-) create mode 100644 src/Android/Avalonia.Android/AvaloniaSplashActivity.cs create mode 100644 src/Android/Avalonia.Android/SingleViewLifetime.cs diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index 33ca511340..3101deb4a9 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -5,16 +5,8 @@ using Avalonia.Android; namespace ControlCatalog.Android { - [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] - public class MainActivity : AvaloniaActivity + [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] + public class MainActivity : AvaloniaActivity { - protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) - { - return base.CustomizeAppBuilder(builder) - .AfterSetup(_ => - { - Pages.EmbedSample.Implementation = new EmbedSampleAndroid(); - }); - } } } diff --git a/samples/ControlCatalog.Android/SplashActivity.cs b/samples/ControlCatalog.Android/SplashActivity.cs index dc292fd37b..908b5f082a 100644 --- a/samples/ControlCatalog.Android/SplashActivity.cs +++ b/samples/ControlCatalog.Android/SplashActivity.cs @@ -1,12 +1,23 @@ using Android.App; using Android.Content; +using Android.Content.PM; using Android.OS; +using Avalonia.Android; namespace ControlCatalog.Android { [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)] - public class SplashActivity : Activity + public class SplashActivity : AvaloniaSplashActivity { + protected override Avalonia.AppBuilder CustomizeAppBuilder(Avalonia.AppBuilder builder) + { + return base.CustomizeAppBuilder(builder) + .AfterSetup(_ => + { + Pages.EmbedSample.Implementation = new EmbedSampleAndroid(); + }); + } + protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaActivity.cs index 4ee4bc1375..9d580b2c46 100644 --- a/src/Android/Avalonia.Android/AvaloniaActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaActivity.cs @@ -1,62 +1,42 @@ +using System; +using Android.App; +using Android.Content; +using Android.Content.Res; using Android.OS; +using Android.Runtime; using AndroidX.AppCompat.App; -using Android.Content.Res; using AndroidX.Lifecycle; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Controls; -using Android.Runtime; -using Android.App; -using Android.Content; -using System; namespace Avalonia.Android { public abstract class AvaloniaActivity : AppCompatActivity { - internal class SingleViewLifetime : ISingleViewApplicationLifetime - { - public AvaloniaView View { get; internal set; } - - public Control MainView - { - get => (Control)View.Content; - set => View.Content = value; - } - } - internal Action ActivityResult; internal AvaloniaView View; internal AvaloniaViewModel _viewModel; - protected abstract AppBuilder CreateAppBuilder(); - protected override void OnCreate(Bundle savedInstanceState) { - var builder = CreateAppBuilder(); + _viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel; - - var lifetime = new SingleViewLifetime(); - - builder.AfterSetup(x => + View = new AvaloniaView(this); + if (_viewModel.Content != null) { - _viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel; + View.Content = _viewModel.Content; + } - View = new AvaloniaView(this); - if (_viewModel.Content != null) - { - View.Content = _viewModel.Content; - } + View.Prepare(); - SetContentView(View); + if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime) + { lifetime.View = View; - - View.Prepare(); - }); - - builder.SetupWithLifetime(lifetime); + } base.OnCreate(savedInstanceState); + + SetContentView(View); } + public object Content { get @@ -90,16 +70,4 @@ namespace Avalonia.Android ActivityResult?.Invoke(requestCode, resultCode, data); } } - - public abstract class AvaloniaActivity : AvaloniaActivity where TApp : Application, new() - { - protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid(); - - protected override AppBuilder CreateAppBuilder() - { - var builder = AppBuilder.Configure(); - - return CustomizeAppBuilder(builder); - } - } } diff --git a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs new file mode 100644 index 0000000000..5b5ebd1bd9 --- /dev/null +++ b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs @@ -0,0 +1,34 @@ +using Android.OS; +using AndroidX.AppCompat.App; +using AndroidX.Lifecycle; + +namespace Avalonia.Android +{ + public abstract class AvaloniaSplashActivity : AppCompatActivity + { + protected abstract AppBuilder CreateAppBuilder(); + + protected override void OnCreate(Bundle? savedInstanceState) + { + base.OnCreate(savedInstanceState); + + var builder = CreateAppBuilder(); + + var lifetime = new SingleViewLifetime(); + + builder.SetupWithLifetime(lifetime); + } + } + + public abstract class AvaloniaSplashActivity : AvaloniaSplashActivity where TApp : Application, new() + { + protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid(); + + protected override AppBuilder CreateAppBuilder() + { + var builder = AppBuilder.Configure(); + + return CustomizeAppBuilder(builder); + } + } +} diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index 94e863210b..34fa121cd7 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -74,7 +74,7 @@ namespace Avalonia.Android class ViewImpl : TopLevelImpl { - public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView) + public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView, true) { View.Focusable = true; View.FocusChange += ViewImpl_FocusChange; diff --git a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs index a9710039f8..e85ed11028 100644 --- a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs +++ b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs @@ -1,4 +1,5 @@ -using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; namespace Avalonia.Android.OpenGL @@ -19,7 +20,8 @@ namespace Avalonia.Android.OpenGL public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info) { - if (EglPlatformOpenGlInterface.TryCreate() is EglPlatformOpenGlInterface egl) + var feature = AvaloniaLocator.Current.GetService(); + if (feature is EglPlatformOpenGlInterface egl) { return new GlPlatformSurface(egl, info); } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index f8eaeba897..1bb74ce897 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -3,20 +3,14 @@ using System.Collections.Generic; using Android.Content; using Android.Graphics; -using Android.Media.TV; -using Android.OS; using Android.Runtime; -using Android.Text; using Android.Views; using Android.Views.InputMethods; -using Android.Widget; using Avalonia.Android.OpenGL; -using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.Specific; using Avalonia.Android.Platform.Specific.Helpers; using Avalonia.Android.Platform.Storage; using Avalonia.Controls; -using Avalonia.Controls.Documents; using Avalonia.Controls.Platform; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input; @@ -29,7 +23,6 @@ using Avalonia.Platform.Storage; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Java.Lang; -using static System.Net.Mime.MediaTypeNames; namespace Avalonia.Android.Platform.SkiaPlatform { diff --git a/src/Android/Avalonia.Android/SingleViewLifetime.cs b/src/Android/Avalonia.Android/SingleViewLifetime.cs new file mode 100644 index 0000000000..eef763a932 --- /dev/null +++ b/src/Android/Avalonia.Android/SingleViewLifetime.cs @@ -0,0 +1,26 @@ +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; + +namespace Avalonia.Android +{ + internal class SingleViewLifetime : ISingleViewApplicationLifetime + { + private AvaloniaView _view; + + public AvaloniaView View + { + get => _view; internal set + { + if (_view != null) + { + _view.Content = null; + _view.Dispose(); + } + _view = value; + _view.Content = MainView; + } + } + + public Control MainView { get; set; } + } +} From 74fafe3b09dbe75b810eb0a590ed588791422a39 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 11 Oct 2022 20:16:19 +0200 Subject: [PATCH 14/53] Optimize GlyphRun allocations --- src/Avalonia.Base/Media/GlyphRun.cs | 81 +++++++- src/Avalonia.Base/Media/GlyphTypeface.cs | 5 + src/Avalonia.Base/Platform/IGlyphRunBuffer.cs | 22 +++ .../Platform/IGlyphTypefaceImpl.cs | 5 + .../Platform/IPlatformRenderInterface.cs | 37 +++- .../HeadlessPlatformRenderInterface.cs | 38 +++- .../HeadlessPlatformStubs.cs | 2 + src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs | 4 + .../Avalonia.Skia/PlatformRenderInterface.cs | 182 ++++++++---------- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 89 +++++---- .../Media/GlyphTypefaceImpl.cs | 2 + .../VisualTree/MockRenderInterface.cs | 15 ++ .../NullRenderingPlatform.cs | 16 +- .../HarfBuzzGlyphTypefaceImpl.cs | 4 + tests/Avalonia.UnitTests/MockGlyphTypeface.cs | 1 + .../MockPlatformRenderInterface.cs | 15 ++ 16 files changed, 354 insertions(+), 164 deletions(-) create mode 100644 src/Avalonia.Base/Platform/IGlyphRunBuffer.cs diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index 2289f98228..ce436ee017 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Platform; using Avalonia.Utilities; @@ -854,9 +855,87 @@ namespace Avalonia.Media throw new InvalidOperationException(); } + _glyphRunImpl = CreateGlyphRunImpl(); + } + + private IGlyphRunImpl CreateGlyphRunImpl() + { + IGlyphRunImpl glyphRunImpl; + var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService(); + var count = GlyphIndices.Count; + var scale = (float)(FontRenderingEmSize / GlyphTypeface.DesignEmHeight); + + if (GlyphOffsets == null) + { + if (GlyphTypeface.IsFixedPitch) + { + var buffer = platformRenderInterface.AllocateGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count); + + var glyphs = buffer.GlyphIndices; + + for (int i = 0; i < glyphs.Length; i++) + { + glyphs[i] = GlyphIndices[i]; + } + + glyphRunImpl = buffer.Build(); + } + else + { + var buffer = platformRenderInterface.AllocateHorizontalGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count); + var glyphs = buffer.GlyphIndices; + var positions = buffer.GlyphPositions; + var width = 0d; + + for (var i = 0; i < count; i++) + { + positions[i] = (float)width; + + if (GlyphAdvances == null) + { + width += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale; + } + else + { + width += GlyphAdvances[i]; + } + + glyphs[i] = GlyphIndices[i]; + } + + glyphRunImpl = buffer.Build(); + } + } + else + { + var buffer = platformRenderInterface.AllocatePositionedGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count); + var glyphs = buffer.GlyphIndices; + var glyphPositions = buffer.GlyphPositions; + var currentX = 0.0; + + for (var i = 0; i < count; i++) + { + var glyphOffset = GlyphOffsets[i]; + + glyphPositions[i] = new PointF((float)(currentX + glyphOffset.X), (float)glyphOffset.Y); + + if (GlyphAdvances == null) + { + currentX += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale; + } + else + { + currentX += GlyphAdvances[i]; + } + + glyphs[i] = GlyphIndices[i]; + } + + glyphRunImpl = buffer.Build(); + } - _glyphRunImpl = platformRenderInterface.CreateGlyphRun(this); + return glyphRunImpl; } void IDisposable.Dispose() diff --git a/src/Avalonia.Base/Media/GlyphTypeface.cs b/src/Avalonia.Base/Media/GlyphTypeface.cs index 45ef04e77f..36b6f3a5f8 100644 --- a/src/Avalonia.Base/Media/GlyphTypeface.cs +++ b/src/Avalonia.Base/Media/GlyphTypeface.cs @@ -67,6 +67,11 @@ namespace Avalonia.Media /// public bool IsFixedPitch => PlatformImpl.IsFixedPitch; + /// + /// Gets the number of glyphs held by this glyph typeface. + /// + public int GlyphCount => PlatformImpl.GlyphCount; + /// /// Returns an glyph index for the specified codepoint. /// diff --git a/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs b/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs new file mode 100644 index 0000000000..c1fc7a5967 --- /dev/null +++ b/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs @@ -0,0 +1,22 @@ +using System; +using System.Drawing; + +namespace Avalonia.Platform +{ + public interface IGlyphRunBuffer + { + Span GlyphIndices { get; } + + IGlyphRunImpl Build(); + } + + public interface IHorizontalGlyphRunBuffer : IGlyphRunBuffer + { + Span GlyphPositions { get; } + } + + public interface IPositionedGlyphRunBuffer : IGlyphRunBuffer + { + Span GlyphPositions { get; } + } +} diff --git a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs b/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs index 415f34fb29..5a38e133ff 100644 --- a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs +++ b/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs @@ -51,6 +51,11 @@ namespace Avalonia.Platform /// bool IsFixedPitch { get; } + /// + /// Gets the number of glyphs held by this glyph typeface. + /// + int GlyphCount { get; } + /// /// Returns an glyph index for the specified codepoint. /// diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index e39a4e23df..93105b21a4 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -171,11 +171,40 @@ namespace Avalonia.Platform IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride); /// - /// Creates a platform implementation of a glyph run. + /// Allocates a platform glyph run buffer. /// - /// The glyph run. - /// - IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun); + /// The glyph typeface. + /// The font rendering em size. + /// The length. + /// An . + /// + /// This buffer only holds glyph indices. + /// + IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + + /// + /// Allocates a horizontal platform glyph run buffer. + /// + /// The glyph typeface. + /// The font rendering em size. + /// The length. + /// An . + /// + /// This buffer holds glyph indices and glyph advances. + /// + IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + + /// + /// Allocates a positioned platform glyph run buffer. + /// + /// The glyph typeface. + /// The font rendering em size. + /// The length. + /// An . + /// + /// This buffer holds glyph indices, glyph advances and glyph positions. + /// + IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); /// /// Gets a value indicating whether the platform directly supports rectangles with rounded corners. diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index cb23c6c336..12a9a016f3 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -110,14 +110,24 @@ namespace Avalonia.Headless return new HeadlessBitmapStub(destinationSize, new Vector(96, 96)); } - public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) + public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) { - return new HeadlessGlyphRunStub(); + return new HeadlessGeometryStub(new Rect(glyphRun.Size)); } - public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) + public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { - return new HeadlessGeometryStub(new Rect(glyphRun.Size)); + return new HeadlessGlyphRunBufferStub(); + } + + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return new HeadlessHorizontalGlyphRunBufferStub(); + } + + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return new HeadlessPositionedGlyphRunBufferStub(); } class HeadlessGeometryStub : IGeometryImpl @@ -203,6 +213,26 @@ namespace Avalonia.Headless public Matrix Transform { get; } } + class HeadlessGlyphRunBufferStub : IGlyphRunBuffer + { + public Span GlyphIndices => Span.Empty; + + public IGlyphRunImpl Build() + { + return new HeadlessGlyphRunStub(); + } + } + + class HeadlessHorizontalGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IHorizontalGlyphRunBuffer + { + public Span GlyphPositions => Span.Empty; + } + + class HeadlessPositionedGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IPositionedGlyphRunBuffer + { + public Span GlyphPositions => Span.Empty; + } + class HeadlessGlyphRunStub : IGlyphRunImpl { public void Dispose() diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index ed51529a96..b3bf92f06b 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -95,6 +95,8 @@ namespace Avalonia.Headless public bool IsFixedPitch => true; + public int GlyphCount => 1337; + public void Dispose() { } diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs index dcb4eac7ca..a3174010a1 100644 --- a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs @@ -55,6 +55,8 @@ namespace Avalonia.Skia IsFixedPitch = Typeface.IsFixedPitch; + GlyphCount = Typeface.GlyphCount; + IsFakeBold = isFakeBold; IsFakeItalic = isFakeItalic; @@ -94,6 +96,8 @@ namespace Avalonia.Skia /// public bool IsFixedPitch { get; } + + public int GlyphCount { get; } public bool IsFakeBold { get; } diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 91fe4fc085..aa9225ba64 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -1,7 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; +using System.IO; using System.Linq; using System.Threading; @@ -12,6 +12,8 @@ using Avalonia.OpenGL.Imaging; using Avalonia.Platform; using Avalonia.Media.Imaging; using SkiaSharp; +using System.Runtime.InteropServices; +using System.Drawing; namespace Avalonia.Skia { @@ -33,13 +35,17 @@ namespace Avalonia.Skia } var gl = AvaloniaLocator.Current.GetService(); - if (gl != null) + if (gl != null) _skiaGpu = new GlSkiaGpu(gl, maxResourceBytes); - - //TODO: SKFont crashes when disposed in finalizer so we keep it alive - GC.SuppressFinalize(s_font); } + + public bool SupportsIndividualRoundRects => true; + + public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; + + public PixelFormat DefaultPixelFormat { get; } + public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect); public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2); @@ -228,133 +234,95 @@ namespace Avalonia.Skia return new WriteableBitmapImpl(size, dpi, format, alphaFormat); } - private static readonly SKFont s_font = new SKFont - { - Subpixel = true, - Edging = SKFontEdging.SubpixelAntialias, - Hinting = SKFontHinting.Full, - LinearMetrics = true - }; - - private static readonly ThreadLocal s_textBlobBuilderThreadLocal = new ThreadLocal(() => new SKTextBlobBuilder()); - - /// - public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) + public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) { - var count = glyphRun.GlyphIndices.Count; - var textBlobBuilder = s_textBlobBuilderThreadLocal.Value; - - var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl; + if (_skiaGpu is IOpenGlAwareSkiaGpu glAware) + return glAware.CreateOpenGlBitmap(size, dpi); + if (_skiaGpu == null) + throw new PlatformNotSupportedException("GPU acceleration is not available"); + throw new PlatformNotSupportedException( + "Current GPU acceleration backend does not support OpenGL integration"); + } - var typeface = glyphTypeface.Typeface; + public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + => new SKGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - s_font.Size = (float)glyphRun.FontRenderingEmSize; - s_font.Typeface = typeface; - s_font.Embolden = glyphTypeface.IsFakeBold; - s_font.SkewX = glyphTypeface.IsFakeItalic ? -0.2f : 0; + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + => new SKHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - SKTextBlob textBlob; + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + => new SKPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - var scale = (float)(glyphRun.FontRenderingEmSize / glyphTypeface.DesignEmHeight); + private abstract class SKGlyphRunBufferBase : IGlyphRunBuffer + { + protected readonly SKTextBlobBuilder _builder; + protected readonly SKFont _font; - if (glyphRun.GlyphOffsets == null) + public SKGlyphRunBufferBase(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { - if (glyphTypeface.IsFixedPitch) - { - var buffer = textBlobBuilder.AllocateRun(s_font, glyphRun.GlyphIndices.Count, 0, 0); - - var glyphs = buffer.GetGlyphSpan(); + _builder = new SKTextBlobBuilder(); - for (int i = 0; i < glyphs.Length; i++) - { - glyphs[i] = glyphRun.GlyphIndices[i]; - } + var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface.PlatformImpl; - textBlob = textBlobBuilder.Build(); - } - else + _font = new SKFont { - var buffer = textBlobBuilder.AllocateHorizontalRun(s_font, count, 0); - - var positions = buffer.GetPositionSpan(); - - var width = 0d; - - for (var i = 0; i < count; i++) - { - positions[i] = (float)width; - - if (glyphRun.GlyphAdvances == null) - { - width += glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale; - } - else - { - width += glyphRun.GlyphAdvances[i]; - } - } - - var glyphs = buffer.GetGlyphSpan(); + Subpixel = true, + Edging = SKFontEdging.SubpixelAntialias, + Hinting = SKFontHinting.Full, + LinearMetrics = true, + Size = fontRenderingEmSize, + Typeface = glyphTypefaceImpl.Typeface, + Embolden = glyphTypefaceImpl.IsFakeBold, + SkewX = glyphTypefaceImpl.IsFakeItalic ? -0.2f : 0 + }; + } - for (int i = 0; i < glyphs.Length; i++) - { - glyphs[i] = glyphRun.GlyphIndices[i]; - } + public abstract Span GlyphIndices { get; } - textBlob = textBlobBuilder.Build(); - } - } - else + public IGlyphRunImpl Build() { - var buffer = textBlobBuilder.AllocatePositionedRun(s_font, count); - - var glyphPositions = buffer.GetPositionSpan(); + return new GlyphRunImpl(_builder.Build()); + } + } - var currentX = 0.0; + private sealed class SKGlyphRunBuffer : SKGlyphRunBufferBase + { + private readonly SKRunBuffer _buffer; - for (var i = 0; i < count; i++) - { - var glyphOffset = glyphRun.GlyphOffsets[i]; - - glyphPositions[i] = new SKPoint((float)(currentX + glyphOffset.X), (float)glyphOffset.Y); - - if (glyphRun.GlyphAdvances == null) - { - currentX += glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale; - } - else - { - currentX += glyphRun.GlyphAdvances[i]; - } - } + public SKGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) + { + _buffer = _builder.AllocateRun(_font, length, 0, 0); + } - var glyphs = buffer.GetGlyphSpan(); + public override Span GlyphIndices => _buffer.GetGlyphSpan(); + } - for (int i = 0; i < glyphs.Length; i++) - { - glyphs[i] = glyphRun.GlyphIndices[i]; - } + private sealed class SKHorizontalGlyphRunBuffer : SKGlyphRunBufferBase, IHorizontalGlyphRunBuffer + { + private readonly SKHorizontalRunBuffer _buffer; - textBlob = textBlobBuilder.Build(); + public SKHorizontalGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) + { + _buffer = _builder.AllocateHorizontalRun(_font, length, 0); } - return new GlyphRunImpl(textBlob); + public override Span GlyphIndices => _buffer.GetGlyphSpan(); + + public Span GlyphPositions => _buffer.GetPositionSpan(); } - public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) + private sealed class SKPositionedGlyphRunBuffer : SKGlyphRunBufferBase, IPositionedGlyphRunBuffer { - if (_skiaGpu is IOpenGlAwareSkiaGpu glAware) - return glAware.CreateOpenGlBitmap(size, dpi); - if (_skiaGpu == null) - throw new PlatformNotSupportedException("GPU acceleration is not available"); - throw new PlatformNotSupportedException( - "Current GPU acceleration backend does not support OpenGL integration"); - } + private readonly SKPositionedRunBuffer _buffer; - public bool SupportsIndividualRoundRects => true; + public SKPositionedGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) + { + _buffer = _builder.AllocatePositionedRun(_font, length); + } - public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; + public override Span GlyphIndices => _buffer.GetGlyphSpan(); - public PixelFormat DefaultPixelFormat { get; } + public Span GlyphPositions => MemoryMarshal.Cast(_buffer.GetPositionSpan()); + } } } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 81fa8c4bce..45744e6efa 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -12,6 +12,8 @@ using SharpDX.DirectWrite; using GlyphRun = Avalonia.Media.GlyphRun; using TextAlignment = Avalonia.Media.TextAlignment; using SharpDX.Mathematics.Interop; +using System.Runtime.InteropServices; +using System.Drawing; namespace Avalonia { @@ -258,69 +260,66 @@ namespace Avalonia.Direct2D1 return new WicBitmapImpl(format, alphaFormat, data, size, dpi, stride); } - public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) + private class DWGlyphRunBuffer : IGlyphRunBuffer { - var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl; + protected readonly SharpDX.DirectWrite.GlyphRun _dwRun; - var glyphCount = glyphRun.GlyphIndices.Count; - - var run = new SharpDX.DirectWrite.GlyphRun + public DWGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { - FontFace = glyphTypeface.FontFace, - FontSize = (float)glyphRun.FontRenderingEmSize - }; - - var indices = new short[glyphCount]; + var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface.PlatformImpl; - for (var i = 0; i < glyphCount; i++) - { - indices[i] = (short)glyphRun.GlyphIndices[i]; + _dwRun = new SharpDX.DirectWrite.GlyphRun + { + FontFace = glyphTypefaceImpl.FontFace, + FontSize = fontRenderingEmSize, + Indices = new short[length] + }; } - run.Indices = indices; - - run.Advances = new float[glyphCount]; + public Span GlyphIndices => MemoryMarshal.Cast(_dwRun.Indices.AsSpan()); - var scale = (float)(glyphRun.FontRenderingEmSize / glyphTypeface.DesignEmHeight); - - if (glyphRun.GlyphAdvances == null) + public IGlyphRunImpl Build() { - for (var i = 0; i < glyphCount; i++) - { - var advance = glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale; - - run.Advances[i] = advance; - } + return new GlyphRunImpl(_dwRun); } - else - { - for (var i = 0; i < glyphCount; i++) - { - var advance = (float)glyphRun.GlyphAdvances[i]; + } - run.Advances[i] = advance; - } + private class DWHorizontalGlyphRunBuffer : DWGlyphRunBuffer, IHorizontalGlyphRunBuffer + { + public DWHorizontalGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + : base(glyphTypeface, fontRenderingEmSize, length) + { + _dwRun.Advances = new float[length]; } - if (glyphRun.GlyphOffsets == null) + public Span GlyphPositions => _dwRun.Advances.AsSpan(); + } + + private class DWPositionedGlyphRunBuffer : DWGlyphRunBuffer, IPositionedGlyphRunBuffer + { + public DWPositionedGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + : base(glyphTypeface, fontRenderingEmSize, length) { - return new GlyphRunImpl(run); + _dwRun.Advances = new float[length]; + _dwRun.Offsets = new GlyphOffset[length]; } - run.Offsets = new GlyphOffset[glyphCount]; + public Span GlyphPositions => MemoryMarshal.Cast(_dwRun.Offsets.AsSpan()); + } - for (var i = 0; i < glyphCount; i++) - { - var (x, y) = glyphRun.GlyphOffsets[i]; + public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return new DWGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); + } - run.Offsets[i] = new GlyphOffset - { - AdvanceOffset = (float)x, - AscenderOffset = (float)y - }; - } + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return new DWHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); + } - return new GlyphRunImpl(run); + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return new DWPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); } public bool SupportsIndividualRoundRects => false; diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs index 4154b44702..443bb8a207 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs @@ -117,6 +117,8 @@ namespace Avalonia.Direct2D1.Media /// public bool IsFixedPitch { get; } + public int GlyphCount { get; set; } + /// public ushort GetGlyph(uint codepoint) { diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs index 1f0b82b465..d21879f349 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs @@ -126,6 +126,21 @@ namespace Avalonia.Base.UnitTests.VisualTree throw new NotImplementedException(); } + public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + throw new NotImplementedException(); + } + + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + throw new NotImplementedException(); + } + + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + throw new NotImplementedException(); + } + class MockStreamGeometry : IStreamGeometryImpl { private MockStreamGeometryContext _impl = new MockStreamGeometryContext(); diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 0193f5d772..a2c2936a4d 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -112,12 +112,22 @@ namespace Avalonia.Benchmarks return new MockFontManagerImpl(); } - public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) + public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) { - return new NullGlyphRun(); + return new MockStreamGeometryImpl(); } - public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) + public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + throw new NotImplementedException(); + } + + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + throw new NotImplementedException(); + } + + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs index 32e0434cd4..04f316e5b4 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs @@ -44,6 +44,8 @@ namespace Avalonia.UnitTests IsFixedPitch = GetGlyphAdvance(GetGlyph('a')) == GetGlyphAdvance(GetGlyph('b')); + GlyphCount = Face.GlyphCount; + IsFakeBold = isFakeBold; IsFakeItalic = isFakeItalic; @@ -79,6 +81,8 @@ namespace Avalonia.UnitTests /// public bool IsFixedPitch { get; } + + public int GlyphCount { get; set; } public bool IsFakeBold { get; } diff --git a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs index c9c59a6e32..0b7beeb4fb 100644 --- a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs +++ b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs @@ -14,6 +14,7 @@ namespace Avalonia.UnitTests public int StrikethroughPosition { get; } public int StrikethroughThickness { get; } public bool IsFixedPitch { get; } + public int GlyphCount => 1337; public ushort GetGlyph(uint codepoint) { diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index f6a127b573..314cc95041 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -152,6 +152,21 @@ namespace Avalonia.UnitTests return Mock.Of(); } + public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return Mock.Of(); + } + + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return Mock.Of(); + } + + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return Mock.Of(); + } + public bool SupportsIndividualRoundRects { get; set; } public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; From 9f74febca316e5219ee8f2014efb3c18c1935158 Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 11 Oct 2022 21:11:11 -0400 Subject: [PATCH 15/53] Add initial TextBox.TextChanged event --- src/Avalonia.Controls/TextBox.cs | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 964a153c8b..76bb37efcf 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -18,6 +18,7 @@ using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Automation.Peers; using System.Diagnostics; +using Avalonia.Threading; namespace Avalonia.Controls { @@ -171,6 +172,10 @@ namespace Avalonia.Controls RoutedEvent.Register( nameof(PastingFromClipboard), RoutingStrategies.Bubble); + public static readonly RoutedEvent TextChangedEvent = + RoutedEvent.Register( + nameof(TextChanged), RoutingStrategies.Bubble); + readonly struct UndoRedoState : IEquatable { public string? Text { get; } @@ -359,8 +364,8 @@ namespace Avalonia.Controls /// public double LineHeight { - get { return GetValue(LineHeightProperty); } - set { SetValue(LineHeightProperty, value); } + get => GetValue(LineHeightProperty); + set => SetValue(LineHeightProperty, value); } [Content] @@ -564,6 +569,18 @@ namespace Avalonia.Controls remove => RemoveHandler(PastingFromClipboardEvent, value); } + /// + /// Occurs when text changes. + /// + /// + /// This event is asynchronous and occurs after the new text is rendered. + /// + public event EventHandler? TextChanged + { + add => AddHandler(TextChangedEvent, value); + remove => RemoveHandler(TextChangedEvent, value); + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _presenter = e.NameScope.Get("PART_TextPresenter"); @@ -1543,7 +1560,16 @@ namespace Avalonia.Controls { if (raiseTextChanged) { - SetAndRaise(TextProperty, ref _text, value); + bool textChanged = SetAndRaise(TextProperty, ref _text, value); + + if (textChanged) + { + Dispatcher.UIThread.Post(() => + { + var eventArgs = new RoutedEventArgs(TextChangedEvent); + RaiseEvent(eventArgs); + }); + } } else { From d3dcf731131c4c8507d1b443e0545ab5b4f83d3c Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 11 Oct 2022 21:43:52 -0400 Subject: [PATCH 16/53] Add TextBox.TextChanging event --- src/Avalonia.Controls/TextBox.cs | 50 +++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 76bb37efcf..eb9f82516e 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -160,22 +160,41 @@ namespace Avalonia.Controls (o, v) => o.UndoLimit = v, unsetValue: -1); + /// + /// Defines the event. + /// public static readonly RoutedEvent CopyingToClipboardEvent = RoutedEvent.Register( nameof(CopyingToClipboard), RoutingStrategies.Bubble); + /// + /// Defines the event. + /// public static readonly RoutedEvent CuttingToClipboardEvent = RoutedEvent.Register( nameof(CuttingToClipboard), RoutingStrategies.Bubble); + /// + /// Defines the event. + /// public static readonly RoutedEvent PastingFromClipboardEvent = RoutedEvent.Register( nameof(PastingFromClipboard), RoutingStrategies.Bubble); + /// + /// Defines the event. + /// public static readonly RoutedEvent TextChangedEvent = RoutedEvent.Register( nameof(TextChanged), RoutingStrategies.Bubble); + /// + /// Defines the event. + /// + public static readonly RoutedEvent TextChangingEvent = + RoutedEvent.Register( + nameof(TextChanging), RoutingStrategies.Bubble); + readonly struct UndoRedoState : IEquatable { public string? Text { get; } @@ -570,17 +589,26 @@ namespace Avalonia.Controls } /// - /// Occurs when text changes. + /// Occurs asynchronously after text changes and the new text is rendered. /// - /// - /// This event is asynchronous and occurs after the new text is rendered. - /// public event EventHandler? TextChanged { add => AddHandler(TextChangedEvent, value); remove => RemoveHandler(TextChangedEvent, value); } + /// + /// Occurs synchronously when text starts to change but before it is rendered. + /// + /// + /// This event occurs just after the property value has been updated. + /// + public event EventHandler? TextChanging + { + add => AddHandler(TextChangingEvent, value); + remove => RemoveHandler(TextChangingEvent, value); + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _presenter = e.NameScope.Get("PART_TextPresenter"); @@ -1564,11 +1592,19 @@ namespace Avalonia.Controls if (textChanged) { + // Note the following sequence of these events (following WinUI) + // 1. TextChanging occurs synchronously when text starts to change but before it is rendered. + // This occurs after the Text property is set. + // 2. TextChanged occurs asynchronously after text changes and the new text is rendered. + + var textChangingEventArgs = new RoutedEventArgs(TextChangingEvent); + RaiseEvent(textChangingEventArgs); + Dispatcher.UIThread.Post(() => { - var eventArgs = new RoutedEventArgs(TextChangedEvent); - RaiseEvent(eventArgs); - }); + var textChangedEventArgs = new RoutedEventArgs(TextChangedEvent); + RaiseEvent(textChangedEventArgs); + }, DispatcherPriority.Normal); } } else From 0869827722b2fea0f3af181fb63b3395ed2ee393 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 12 Oct 2022 07:41:06 +0000 Subject: [PATCH 17/53] Address Review --- samples/ControlCatalog.Android/MainActivity.cs | 4 ++-- samples/MobileSandbox.Android/MainActivity.cs | 4 ++-- samples/MobileSandbox.Android/SplashActivity.cs | 4 ++-- ...valoniaActivity.cs => AvaloniaMainActivity.cs} | 15 +++++++-------- src/Android/Avalonia.Android/AvaloniaViewModel.cs | 11 ----------- .../Platform/SkiaPlatform/TopLevelImpl.cs | 2 +- .../Platform/Storage/AndroidStorageProvider.cs | 4 ++-- 7 files changed, 16 insertions(+), 28 deletions(-) rename src/Android/Avalonia.Android/{AvaloniaActivity.cs => AvaloniaMainActivity.cs} (78%) delete mode 100644 src/Android/Avalonia.Android/AvaloniaViewModel.cs diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index 3101deb4a9..62c582610c 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -5,8 +5,8 @@ using Avalonia.Android; namespace ControlCatalog.Android { - [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] - public class MainActivity : AvaloniaActivity + [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] + public class MainActivity : AvaloniaMainActivity { } } diff --git a/samples/MobileSandbox.Android/MainActivity.cs b/samples/MobileSandbox.Android/MainActivity.cs index ac9242dd52..d65f0dec92 100644 --- a/samples/MobileSandbox.Android/MainActivity.cs +++ b/samples/MobileSandbox.Android/MainActivity.cs @@ -5,8 +5,8 @@ using Avalonia.Android; namespace MobileSandbox.Android { - [Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] - public class MainActivity : AvaloniaActivity + [Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] + public class MainActivity : AvaloniaMainActivity { } } diff --git a/samples/MobileSandbox.Android/SplashActivity.cs b/samples/MobileSandbox.Android/SplashActivity.cs index c26371d6fe..ced092554d 100644 --- a/samples/MobileSandbox.Android/SplashActivity.cs +++ b/samples/MobileSandbox.Android/SplashActivity.cs @@ -1,11 +1,11 @@ using Android.App; using Android.Content; -using Android.OS; +using Avalonia.Android; namespace MobileSandbox.Android { [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)] - public class SplashActivity : Activity + public class SplashActivity : AvaloniaSplashActivity { protected override void OnResume() { diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs similarity index 78% rename from src/Android/Avalonia.Android/AvaloniaActivity.cs rename to src/Android/Avalonia.Android/AvaloniaMainActivity.cs index 9d580b2c46..705fa3c59d 100644 --- a/src/Android/Avalonia.Android/AvaloniaActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs @@ -9,20 +9,19 @@ using AndroidX.Lifecycle; namespace Avalonia.Android { - public abstract class AvaloniaActivity : AppCompatActivity + public abstract class AvaloniaMainActivity : AppCompatActivity { + internal static object ViewContent; + internal Action ActivityResult; internal AvaloniaView View; - internal AvaloniaViewModel _viewModel; protected override void OnCreate(Bundle savedInstanceState) { - _viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel; - View = new AvaloniaView(this); - if (_viewModel.Content != null) + if (ViewContent != null) { - View.Content = _viewModel.Content; + View.Content = ViewContent; } View.Prepare(); @@ -41,11 +40,11 @@ namespace Avalonia.Android { get { - return _viewModel.Content; + return ViewContent; } set { - _viewModel.Content = value; + ViewContent = value; if (View != null) View.Content = value; } diff --git a/src/Android/Avalonia.Android/AvaloniaViewModel.cs b/src/Android/Avalonia.Android/AvaloniaViewModel.cs deleted file mode 100644 index 1b2c00987a..0000000000 --- a/src/Android/Avalonia.Android/AvaloniaViewModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Avalonia.Android -{ - internal class AvaloniaViewModel : AndroidX.Lifecycle.ViewModel - { - public object Content { get; set; } - } -} diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 1bb74ce897..eb11509bfc 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -52,7 +52,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling); NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView); - StorageProvider = new AndroidStorageProvider((AvaloniaActivity)avaloniaView.Context); + StorageProvider = new AndroidStorageProvider((AvaloniaMainActivity)avaloniaView.Context); } public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) => diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs index 653f450ec8..3a1a9e76ea 100644 --- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs +++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs @@ -14,10 +14,10 @@ namespace Avalonia.Android.Platform.Storage; internal class AndroidStorageProvider : IStorageProvider { - private readonly AvaloniaActivity _activity; + private readonly AvaloniaMainActivity _activity; private int _lastRequestCode = 20000; - public AndroidStorageProvider(AvaloniaActivity activity) + public AndroidStorageProvider(AvaloniaMainActivity activity) { _activity = activity; } From a432f7d5b15b2f5ee5e2d22d1c55eac826407f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Wed, 12 Oct 2022 10:12:53 +0200 Subject: [PATCH 18/53] Add SupportedOSPlatform browser --- samples/ControlCatalog.Web/Program.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/samples/ControlCatalog.Web/Program.cs b/samples/ControlCatalog.Web/Program.cs index 52acabb0fa..7d05c8e462 100644 --- a/samples/ControlCatalog.Web/Program.cs +++ b/samples/ControlCatalog.Web/Program.cs @@ -1,8 +1,11 @@ +using System.Runtime.Versioning; using Avalonia; using Avalonia.Web; using ControlCatalog; using ControlCatalog.Web; +[assembly:SupportedOSPlatform("browser")] + internal partial class Program { private static void Main(string[] args) From 4de98100ea6e7277aafe8dc2d74ace4863c88457 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 12 Oct 2022 12:06:54 +0200 Subject: [PATCH 19/53] feat: Enable net Analyzer --- .editorconfig | 3 +++ Avalonia.sln | 3 +++ build/NetAnalyzers.props | 5 +++++ src/Directory.Build.props | 1 + 4 files changed, 12 insertions(+) create mode 100644 build/NetAnalyzers.props diff --git a/.editorconfig b/.editorconfig index 128b6f1712..179491eece 100644 --- a/.editorconfig +++ b/.editorconfig @@ -134,6 +134,9 @@ csharp_space_between_parentheses = false csharp_space_between_square_brackets = false space_within_single_line_array_initializer_braces = true +#Net Analyzer +dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed. + # Wrapping preferences csharp_wrap_before_ternary_opsigns = false diff --git a/Avalonia.sln b/Avalonia.sln index 61592e1d44..c000f56d09 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -90,6 +90,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}" ProjectSection(SolutionItems) = preProject build\ApiDiff.props = build\ApiDiff.props + build\AvaloniaPublicKey.props = build\AvaloniaPublicKey.props build\Base.props = build\Base.props build\Binding.props = build\Binding.props build\CoreLibraries.props = build\CoreLibraries.props @@ -102,8 +103,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\Microsoft.CSharp.props = build\Microsoft.CSharp.props build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props build\Moq.props = build\Moq.props + build\NetAnalyzers.props = build\NetAnalyzers.props build\NetCore.props = build\NetCore.props build\NetFX.props = build\NetFX.props + build\NullableEnable.props = build\NullableEnable.props build\ReactiveUI.props = build\ReactiveUI.props build\ReferenceCoreLibraries.props = build\ReferenceCoreLibraries.props build\Rx.props = build\Rx.props diff --git a/build/NetAnalyzers.props b/build/NetAnalyzers.props new file mode 100644 index 0000000000..dfca9ecf9e --- /dev/null +++ b/build/NetAnalyzers.props @@ -0,0 +1,5 @@ + + + true + + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 869602e452..55a1014188 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,6 +2,7 @@ + Shared\_ModuleInitializer.cs From 5a5080b1a5698456961624de63af0c0e4ba203ec Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 12 Oct 2022 14:14:46 +0200 Subject: [PATCH 20/53] Rework GlyphTypeface --- samples/RenderDemo/Pages/GlyphRunPage.xaml.cs | 4 +- src/Avalonia.Base/Media/FontManager.cs | 12 +- src/Avalonia.Base/Media/FontMetrics.cs | 63 +++++++++ src/Avalonia.Base/Media/GlyphRun.cs | 18 +-- src/Avalonia.Base/Media/GlyphTypeface.cs | 130 ------------------ .../IGlyphTypeface.cs} | 73 ++++------ src/Avalonia.Base/Media/TextDecoration.cs | 16 +-- .../Media/TextFormatting/ShapedBuffer.cs | 6 +- .../TextFormatting/ShapedTextCharacters.cs | 9 +- .../Media/TextFormatting/TextLineImpl.cs | 26 ++-- .../{FontMetrics.cs => TextMetrics.cs} | 24 ++-- .../Media/TextFormatting/TextShaperOptions.cs | 4 +- .../Media/TextFormatting/Unicode/Codepoint.cs | 5 +- src/Avalonia.Base/Media/Typeface.cs | 2 +- .../Platform/IFontManagerImpl.cs | 2 +- .../Platform/IPlatformRenderInterface.cs | 6 +- .../Composition/Server/FpsCounter.cs | 2 +- src/Avalonia.Base/Utilities/ReadOnlySlice.cs | 2 + .../HeadlessPlatformStubs.cs | 22 ++- src/Skia/Avalonia.Skia/FontManagerImpl.cs | 2 +- src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs | 92 +++++-------- .../Avalonia.Skia/PlatformRenderInterface.cs | 18 +-- .../Media/FontManagerImpl.cs | 2 +- .../Media/GlyphTypefaceImpl.cs | 105 ++++++-------- .../Media/CustomFontManagerImpl.cs | 2 +- .../TextFormatting/TextFormatterTests.cs | 2 +- .../Media/TextFormatting/TextLayoutTests.cs | 4 +- .../HarfBuzzFontManagerImpl.cs | 2 +- .../HarfBuzzGlyphTypefaceImpl.cs | 96 ++++++------- .../Avalonia.UnitTests/MockFontManagerImpl.cs | 2 +- tests/Avalonia.UnitTests/MockGlyphTypeface.cs | 34 +++-- 31 files changed, 352 insertions(+), 435 deletions(-) create mode 100644 src/Avalonia.Base/Media/FontMetrics.cs delete mode 100644 src/Avalonia.Base/Media/GlyphTypeface.cs rename src/Avalonia.Base/{Platform/IGlyphTypefaceImpl.cs => Media/IGlyphTypeface.cs} (53%) rename src/Avalonia.Base/Media/TextFormatting/{FontMetrics.cs => TextMetrics.cs} (69%) diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs index 674ed8e61f..5c31e138e0 100644 --- a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs +++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs @@ -22,7 +22,7 @@ namespace RenderDemo.Pages public class GlyphRunControl : Control { - private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; + private IGlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; private readonly Random _rand = new Random(); private ushort[] _glyphIndices = new ushort[1]; private char[] _characters = new char[1]; @@ -81,7 +81,7 @@ namespace RenderDemo.Pages public class GlyphRunGeometryControl : Control { - private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; + private IGlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; private readonly Random _rand = new Random(); private ushort[] _glyphIndices = new ushort[1]; private char[] _characters = new char[1]; diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs index 37091b82e3..dcb29ffebf 100644 --- a/src/Avalonia.Base/Media/FontManager.cs +++ b/src/Avalonia.Base/Media/FontManager.cs @@ -13,8 +13,8 @@ namespace Avalonia.Media /// public sealed class FontManager { - private readonly ConcurrentDictionary _glyphTypefaceCache = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _glyphTypefaceCache = + new ConcurrentDictionary(); private readonly FontFamily _defaultFontFamily; private readonly IReadOnlyList? _fontFallbacks; @@ -81,13 +81,13 @@ namespace Avalonia.Media PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates); /// - /// Returns a new , or an existing one if a matching exists. + /// Returns a new , or an existing one if a matching exists. /// /// The typeface. /// - /// The . + /// The . /// - public GlyphTypeface GetOrAddGlyphTypeface(Typeface typeface) + public IGlyphTypeface GetOrAddGlyphTypeface(Typeface typeface) { while (true) { @@ -96,7 +96,7 @@ namespace Avalonia.Media return glyphTypeface; } - glyphTypeface = new GlyphTypeface(typeface); + glyphTypeface = PlatformImpl.CreateGlyphTypeface(typeface); if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface)) { diff --git a/src/Avalonia.Base/Media/FontMetrics.cs b/src/Avalonia.Base/Media/FontMetrics.cs new file mode 100644 index 0000000000..3924119a3e --- /dev/null +++ b/src/Avalonia.Base/Media/FontMetrics.cs @@ -0,0 +1,63 @@ +namespace Avalonia.Media +{ + /// + /// The font metrics is holding information about a font's ascent, descent, etc. in design em units. + /// + public readonly struct FontMetrics + { + /// + /// Gets the font design units per em. + /// + public short DesignEmHeight { get; init; } + + /// + /// A value indicating whether all glyphs in the font have the same advancement. + /// + public bool IsFixedPitch { get; init; } + + /// + /// Gets the recommended distance above the baseline in design em size. + /// + public int Ascent { get; init; } + + /// + /// Gets the recommended distance under the baseline in design em size. + /// + public int Descent { get; init; } + + /// + /// Gets the recommended additional space between two lines of text in design em size. + /// + public int LineGap { get; init; } + + /// + /// Gets the recommended line spacing of a formed text line. + /// + public int LineSpacing => Descent - Ascent + LineGap; + + /// + /// Gets a value that indicates the distance of the underline from the baseline in design em size. + /// + public int UnderlinePosition { get; init; } + + /// + /// Gets a value that indicates the thickness of the underline in design em size. + /// + public int UnderlineThickness { get; init; } + + /// + /// Gets a value that indicates the distance of the strikethrough from the baseline in design em size. + /// + public int StrikethroughPosition { get; init; } + + /// + /// Gets a value that indicates the thickness of the underline in design em size. + /// + public int StrikethroughThickness { get; init; } + } +} + +namespace System.Runtime.CompilerServices +{ + public class IsExternalInit { } +} diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index ce436ee017..a1cb00e209 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -16,7 +16,7 @@ namespace Avalonia.Media private static readonly IComparer s_descendingComparer = new ReverseComparer(); private IGlyphRunImpl? _glyphRunImpl; - private GlyphTypeface _glyphTypeface; + private IGlyphTypeface _glyphTypeface; private double _fontRenderingEmSize; private int _biDiLevel; private Point? _baselineOrigin; @@ -43,7 +43,7 @@ namespace Avalonia.Media /// The glyph clusters. /// The bidi level. public GlyphRun( - GlyphTypeface glyphTypeface, + IGlyphTypeface glyphTypeface, double fontRenderingEmSize, ReadOnlySlice characters, IReadOnlyList glyphIndices, @@ -70,9 +70,9 @@ namespace Avalonia.Media } /// - /// Gets the for the . + /// Gets the for the . /// - public GlyphTypeface GlyphTypeface => _glyphTypeface; + public IGlyphTypeface GlyphTypeface => _glyphTypeface; /// /// Gets or sets the em size used for rendering the . @@ -172,7 +172,7 @@ namespace Avalonia.Media /// /// Gets the scale of the current /// - internal double Scale => FontRenderingEmSize / GlyphTypeface.DesignEmHeight; + internal double Scale => FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight; /// /// Returns true if the text direction is left-to-right. Otherwise, returns false. @@ -613,7 +613,7 @@ namespace Avalonia.Media /// The baseline origin. private Point CalculateBaselineOrigin() { - return new Point(0, -GlyphTypeface.Ascent * Scale); + return new Point(0, -GlyphTypeface.Metrics.Ascent * Scale); } private GlyphRunMetrics CreateGlyphRunMetrics() @@ -637,7 +637,7 @@ namespace Avalonia.Media } var isReversed = firstCluster > lastCluster; - var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; + var height = GlyphTypeface.Metrics.LineSpacing * Scale; var widthIncludingTrailingWhitespace = 0d; var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount); @@ -864,11 +864,11 @@ namespace Avalonia.Media var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService(); var count = GlyphIndices.Count; - var scale = (float)(FontRenderingEmSize / GlyphTypeface.DesignEmHeight); + var scale = (float)(FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight); if (GlyphOffsets == null) { - if (GlyphTypeface.IsFixedPitch) + if (GlyphTypeface.Metrics.IsFixedPitch) { var buffer = platformRenderInterface.AllocateGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count); diff --git a/src/Avalonia.Base/Media/GlyphTypeface.cs b/src/Avalonia.Base/Media/GlyphTypeface.cs deleted file mode 100644 index 36b6f3a5f8..0000000000 --- a/src/Avalonia.Base/Media/GlyphTypeface.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using Avalonia.Platform; - -namespace Avalonia.Media -{ - public sealed class GlyphTypeface : IDisposable - { - public GlyphTypeface(Typeface typeface) - : this(FontManager.Current.PlatformImpl.CreateGlyphTypeface(typeface)) - { - } - - public GlyphTypeface(IGlyphTypefaceImpl platformImpl) - { - PlatformImpl = platformImpl; - } - - public IGlyphTypefaceImpl PlatformImpl { get; } - - /// - /// Gets the font design units per em. - /// - public short DesignEmHeight => PlatformImpl.DesignEmHeight; - - /// - /// Gets the recommended distance above the baseline in design em size. - /// - public int Ascent => PlatformImpl.Ascent; - - /// - /// Gets the recommended distance under the baseline in design em size. - /// - public int Descent => PlatformImpl.Descent; - - /// - /// Gets the recommended additional space between two lines of text in design em size. - /// - public int LineGap => PlatformImpl.LineGap; - - /// - /// Gets the recommended line height. - /// - public int LineHeight => Descent - Ascent + LineGap; - - /// - /// Gets a value that indicates the distance of the underline from the baseline in design em size. - /// - public int UnderlinePosition => PlatformImpl.UnderlinePosition; - - /// - /// Gets a value that indicates the thickness of the underline in design em size. - /// - public int UnderlineThickness => PlatformImpl.UnderlineThickness; - - /// - /// Gets a value that indicates the distance of the strikethrough from the baseline in design em size. - /// - public int StrikethroughPosition => PlatformImpl.StrikethroughPosition; - - /// - /// Gets a value that indicates the thickness of the underline in design em size. - /// - public int StrikethroughThickness => PlatformImpl.StrikethroughThickness; - - /// - /// A value indicating whether all glyphs in the font have the same advancement. - /// - public bool IsFixedPitch => PlatformImpl.IsFixedPitch; - - /// - /// Gets the number of glyphs held by this glyph typeface. - /// - public int GlyphCount => PlatformImpl.GlyphCount; - - /// - /// Returns an glyph index for the specified codepoint. - /// - /// - /// Returns a replacement glyph if a glyph isn't found. - /// - /// The codepoint. - /// - /// A glyph index. - /// - public ushort GetGlyph(uint codepoint) => PlatformImpl.GetGlyph(codepoint); - - /// - /// Tries to get an glyph index for specified codepoint. - /// - /// The codepoint. - /// A glyph index. - /// - /// true if an glyph index was found, false otherwise. - /// - public bool TryGetGlyph(uint codepoint, out ushort glyph) - { - glyph = PlatformImpl.GetGlyph(codepoint); - - return glyph != 0; - } - - /// - /// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as 0. - /// - /// The codepoints to map. - /// - public ushort[] GetGlyphs(ReadOnlySpan codepoints) => PlatformImpl.GetGlyphs(codepoints); - - /// - /// Returns the glyph advance for the specified glyph. - /// - /// The glyph. - /// - /// The advance. - /// - public int GetGlyphAdvance(ushort glyph) => PlatformImpl.GetGlyphAdvance(glyph); - - /// - /// Returns an array of glyph advances in design em size. - /// - /// The glyph indices. - /// - public int[] GetGlyphAdvances(ReadOnlySpan glyphs) => PlatformImpl.GetGlyphAdvances(glyphs); - - void IDisposable.Dispose() - { - PlatformImpl?.Dispose(); - } - } -} diff --git a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs b/src/Avalonia.Base/Media/IGlyphTypeface.cs similarity index 53% rename from src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs rename to src/Avalonia.Base/Media/IGlyphTypeface.cs index 5a38e133ff..de2a2309ee 100644 --- a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs +++ b/src/Avalonia.Base/Media/IGlyphTypeface.cs @@ -1,60 +1,23 @@ using System; using Avalonia.Metadata; -namespace Avalonia.Platform +namespace Avalonia.Media { [Unstable] - public interface IGlyphTypefaceImpl : IDisposable + public interface IGlyphTypeface : IDisposable { /// - /// Gets the font design units per em. - /// - short DesignEmHeight { get; } - - /// - /// Gets the recommended distance above the baseline in design em size. - /// - int Ascent { get; } - - /// - /// Gets the recommended distance under the baseline in design em size. - /// - int Descent { get; } - - /// - /// Gets the recommended additional space between two lines of text in design em size. - /// - int LineGap { get; } - - /// - /// Gets a value that indicates the distance of the underline from the baseline in design em size. - /// - int UnderlinePosition { get; } - - /// - /// Gets a value that indicates the thickness of the underline in design em size. - /// - int UnderlineThickness { get; } - - /// - /// Gets a value that indicates the distance of the strikethrough from the baseline in design em size. - /// - int StrikethroughPosition { get; } - - /// - /// Gets a value that indicates the thickness of the underline in design em size. - /// - int StrikethroughThickness { get; } - - /// - /// A value indicating whether all glyphs in the font have the same advancement. + /// Gets the number of glyphs held by this glyph typeface. /// - bool IsFixedPitch { get; } + int GlyphCount { get; } /// - /// Gets the number of glyphs held by this glyph typeface. + /// Gets the font metrics. /// - int GlyphCount { get; } + /// + /// The font metrics. + /// + FontMetrics Metrics { get; } /// /// Returns an glyph index for the specified codepoint. @@ -68,6 +31,16 @@ namespace Avalonia.Platform /// ushort GetGlyph(uint codepoint); + /// + /// Tries to get an glyph index for specified codepoint. + /// + /// The codepoint. + /// A glyph index. + /// + /// true if an glyph index was found, false otherwise. + /// + bool TryGetGlyph(uint codepoint, out ushort glyph); + /// /// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as 0. /// @@ -94,5 +67,13 @@ namespace Avalonia.Platform /// An array of glyph advances. /// int[] GetGlyphAdvances(ReadOnlySpan glyphs); + + /// + /// Returns the contents of the table data for the specified tag. + /// + /// The table tag to get the data for. + /// The contents of the table data for the specified tag. + /// Returns true if the content exists, otherwise false. + bool TryGetTable(uint tag, out byte[] table); } } diff --git a/src/Avalonia.Base/Media/TextDecoration.cs b/src/Avalonia.Base/Media/TextDecoration.cs index 4c9764af96..0a7328125a 100644 --- a/src/Avalonia.Base/Media/TextDecoration.cs +++ b/src/Avalonia.Base/Media/TextDecoration.cs @@ -155,9 +155,9 @@ namespace Avalonia.Media /// /// The drawing context. /// The decorated run. - /// The font metrics of the decorated run. + /// The font metrics of the decorated run. /// The default brush that is used to draw the decoration. - internal void Draw(DrawingContext drawingContext, GlyphRun glyphRun, FontMetrics fontMetrics, IBrush defaultBrush) + internal void Draw(DrawingContext drawingContext, GlyphRun glyphRun, TextMetrics textMetrics, IBrush defaultBrush) { var baselineOrigin = glyphRun.BaselineOrigin; var thickness = StrokeThickness; @@ -168,16 +168,16 @@ namespace Avalonia.Media switch (Location) { case TextDecorationLocation.Underline: - thickness = fontMetrics.UnderlineThickness; + thickness = textMetrics.UnderlineThickness; break; case TextDecorationLocation.Strikethrough: - thickness = fontMetrics.StrikethroughThickness; + thickness = textMetrics.StrikethroughThickness; break; } break; case TextDecorationUnit.FontRenderingEmSize: - thickness = fontMetrics.FontRenderingEmSize * thickness; + thickness = textMetrics.FontRenderingEmSize * thickness; break; } @@ -189,17 +189,17 @@ namespace Avalonia.Media origin += glyphRun.BaselineOrigin; break; case TextDecorationLocation.Strikethrough: - origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.StrikethroughPosition); + origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.StrikethroughPosition); break; case TextDecorationLocation.Underline: - origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.UnderlinePosition); + origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.UnderlinePosition); break; } switch (StrokeOffsetUnit) { case TextDecorationUnit.FontRenderingEmSize: - origin += new Point(0, StrokeOffset * fontMetrics.FontRenderingEmSize); + origin += new Point(0, StrokeOffset * textMetrics.FontRenderingEmSize); break; case TextDecorationUnit.Pixel: origin += new Point(0, StrokeOffset); diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs index 47a6334e39..85924a3d32 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs @@ -8,13 +8,13 @@ namespace Avalonia.Media.TextFormatting { private static readonly IComparer s_clusterComparer = new CompareClusters(); - public ShapedBuffer(ReadOnlySlice text, int length, GlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) + public ShapedBuffer(ReadOnlySlice text, int length, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) : this(text, new GlyphInfo[length], glyphTypeface, fontRenderingEmSize, bidiLevel) { } - internal ShapedBuffer(ReadOnlySlice text, ArraySlice glyphInfos, GlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) + internal ShapedBuffer(ReadOnlySlice text, ArraySlice glyphInfos, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) { Text = text; GlyphInfos = glyphInfos; @@ -29,7 +29,7 @@ namespace Avalonia.Media.TextFormatting public int Length => GlyphInfos.Length; - public GlyphTypeface GlyphTypeface { get; } + public IGlyphTypeface GlyphTypeface { get; } public double FontRenderingEmSize { get; } diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs index 53287a264d..21101f462c 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Utilities; @@ -18,7 +17,7 @@ namespace Avalonia.Media.TextFormatting Text = shapedBuffer.Text; Properties = properties; TextSourceLength = Text.Length; - FontMetrics = new FontMetrics(properties.Typeface, properties.FontRenderingEmSize); + TextMetrics = new TextMetrics(properties.Typeface, properties.FontRenderingEmSize); } public bool IsReversed { get; private set; } @@ -36,9 +35,9 @@ namespace Avalonia.Media.TextFormatting /// public override int TextSourceLength { get; } - public FontMetrics FontMetrics { get; } + public TextMetrics TextMetrics { get; } - public override double Baseline => -FontMetrics.Ascent; + public override double Baseline => -TextMetrics.Ascent; public override Size Size => GlyphRun.Size; @@ -89,7 +88,7 @@ namespace Avalonia.Media.TextFormatting foreach (var textDecoration in Properties.TextDecorations) { - textDecoration.Draw(drawingContext, GlyphRun, FontMetrics, Properties.ForegroundBrush); + textDecoration.Draw(drawingContext, GlyphRun, TextMetrics, Properties.ForegroundBrush); } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index aba8008fb9..96f88d1f44 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -1378,17 +1378,17 @@ namespace Avalonia.Media.TextFormatting private TextLineMetrics CreateLineMetrics() { - var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface; + var fontMetrics = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface.Metrics; var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize; - var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight; + var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight; var width = 0d; var widthIncludingWhitespace = 0d; var trailingWhitespaceLength = 0; var newLineLength = 0; - var ascent = glyphTypeface.Ascent * scale; - var descent = glyphTypeface.Descent * scale; - var lineGap = glyphTypeface.LineGap * scale; + var ascent = fontMetrics.Ascent * scale; + var descent = fontMetrics.Descent * scale; + var lineGap = fontMetrics.LineGap * scale; var height = descent - ascent + lineGap; @@ -1400,26 +1400,26 @@ namespace Avalonia.Media.TextFormatting { case ShapedTextCharacters textRun: { - var fontMetrics = - new FontMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize); + var textMetrics = + new TextMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize); if (fontRenderingEmSize < textRun.Properties.FontRenderingEmSize) { fontRenderingEmSize = textRun.Properties.FontRenderingEmSize; - if (ascent > fontMetrics.Ascent) + if (ascent > textMetrics.Ascent) { - ascent = fontMetrics.Ascent; + ascent = textMetrics.Ascent; } - if (descent < fontMetrics.Descent) + if (descent < textMetrics.Descent) { - descent = fontMetrics.Descent; + descent = textMetrics.Descent; } - if (lineGap < fontMetrics.LineGap) + if (lineGap < textMetrics.LineGap) { - lineGap = fontMetrics.LineGap; + lineGap = textMetrics.LineGap; } if (descent - ascent + lineGap > height) diff --git a/src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs b/src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs similarity index 69% rename from src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs rename to src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs index e01bba00a4..0382e66b5a 100644 --- a/src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs @@ -1,33 +1,33 @@ namespace Avalonia.Media.TextFormatting { /// - /// A metric that holds information about font specific measurements. + /// A metric that holds information about text specific measurements. /// - public readonly struct FontMetrics + public readonly struct TextMetrics { - public FontMetrics(Typeface typeface, double fontRenderingEmSize) + public TextMetrics(Typeface typeface, double fontRenderingEmSize) { - var glyphTypeface = typeface.GlyphTypeface; + var fontMetrics = typeface.GlyphTypeface.Metrics; - var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight; + var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight; FontRenderingEmSize = fontRenderingEmSize; - Ascent = glyphTypeface.Ascent * scale; + Ascent = fontMetrics.Ascent * scale; - Descent = glyphTypeface.Descent * scale; + Descent = fontMetrics.Descent * scale; - LineGap = glyphTypeface.LineGap * scale; + LineGap = fontMetrics.LineGap * scale; LineHeight = Descent - Ascent + LineGap; - UnderlineThickness = glyphTypeface.UnderlineThickness * scale; + UnderlineThickness = fontMetrics.UnderlineThickness * scale; - UnderlinePosition = glyphTypeface.UnderlinePosition * scale; + UnderlinePosition = fontMetrics.UnderlinePosition * scale; - StrikethroughThickness = glyphTypeface.StrikethroughThickness * scale; + StrikethroughThickness = fontMetrics.StrikethroughThickness * scale; - StrikethroughPosition = glyphTypeface.StrikethroughPosition * scale; + StrikethroughPosition = fontMetrics.StrikethroughPosition * scale; } /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs index 4e75bb921e..0d00bed51e 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs @@ -8,7 +8,7 @@ namespace Avalonia.Media.TextFormatting public readonly struct TextShaperOptions { public TextShaperOptions( - GlyphTypeface typeface, + IGlyphTypeface typeface, double fontRenderingEmSize = 12, sbyte bidiLevel = 0, CultureInfo? culture = null, @@ -24,7 +24,7 @@ namespace Avalonia.Media.TextFormatting /// /// Get the typeface. /// - public GlyphTypeface Typeface { get; } + public IGlyphTypeface Typeface { get; } /// /// Get the font rendering em size. /// diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs index ce9cdde044..de40839853 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System; +using System.Runtime.CompilerServices; using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting.Unicode @@ -165,7 +166,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// The index to read at. /// The count of character that were read. /// - public static Codepoint ReadAt(ReadOnlySlice text, int index, out int count) + public static Codepoint ReadAt(ReadOnlySpan text, int index, out int count) { count = 1; diff --git a/src/Avalonia.Base/Media/Typeface.cs b/src/Avalonia.Base/Media/Typeface.cs index f0daa841d9..e6047bf96c 100644 --- a/src/Avalonia.Base/Media/Typeface.cs +++ b/src/Avalonia.Base/Media/Typeface.cs @@ -81,7 +81,7 @@ namespace Avalonia.Media /// /// The glyph typeface. /// - public GlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this); + public IGlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this); public static bool operator !=(Typeface a, Typeface b) { diff --git a/src/Avalonia.Base/Platform/IFontManagerImpl.cs b/src/Avalonia.Base/Platform/IFontManagerImpl.cs index 932249bd52..cd6e64abaf 100644 --- a/src/Avalonia.Base/Platform/IFontManagerImpl.cs +++ b/src/Avalonia.Base/Platform/IFontManagerImpl.cs @@ -43,6 +43,6 @@ namespace Avalonia.Platform /// 0 /// The created glyph typeface. Can be Null if it was not possible to create a glyph typeface. /// - IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface); + IGlyphTypeface CreateGlyphTypeface(Typeface typeface); } } diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index 93105b21a4..9d0d7974b4 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -180,7 +180,7 @@ namespace Avalonia.Platform /// /// This buffer only holds glyph indices. /// - IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); /// /// Allocates a horizontal platform glyph run buffer. @@ -192,7 +192,7 @@ namespace Avalonia.Platform /// /// This buffer holds glyph indices and glyph advances. /// - IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); /// /// Allocates a positioned platform glyph run buffer. @@ -204,7 +204,7 @@ namespace Avalonia.Platform /// /// This buffer holds glyph indices, glyph advances and glyph positions. /// - IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); /// /// Gets a value indicating whether the platform directly supports rectangles with rounded corners. diff --git a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs index b0b3982ed5..06fb526736 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs @@ -25,7 +25,7 @@ internal class FpsCounter // ASCII chars private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1]; - public FpsCounter(GlyphTypeface typeface) + public FpsCounter(IGlyphTypeface typeface) { for (var c = FirstChar; c <= LastChar; c++) { diff --git a/src/Avalonia.Base/Utilities/ReadOnlySlice.cs b/src/Avalonia.Base/Utilities/ReadOnlySlice.cs index ad545b2923..583a3139b9 100644 --- a/src/Avalonia.Base/Utilities/ReadOnlySlice.cs +++ b/src/Avalonia.Base/Utilities/ReadOnlySlice.cs @@ -214,6 +214,8 @@ namespace Avalonia.Utilities return new ReadOnlySlice(memory); } + public static implicit operator ReadOnlySpan(ReadOnlySlice slice) => slice.Span; + internal class ReadOnlySliceDebugView { private readonly ReadOnlySlice _readOnlySlice; diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index b3bf92f06b..c8ac947c16 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -75,8 +75,13 @@ namespace Avalonia.Headless public TimeSpan TouchDoubleClickTime => DoubleClickTime; } - class HeadlessGlyphTypefaceImpl : IGlyphTypefaceImpl + class HeadlessGlyphTypefaceImpl : IGlyphTypeface { + public FontMetrics Metrics => new FontMetrics + { + + }; + public short DesignEmHeight => 10; public int Ascent => 5; @@ -106,6 +111,13 @@ namespace Avalonia.Headless return 1; } + public bool TryGetGlyph(uint codepoint, out ushort glyph) + { + glyph = 1; + + return true; + } + public int GetGlyphAdvance(ushort glyph) { return 1; @@ -120,6 +132,12 @@ namespace Avalonia.Headless { return codepoints.ToArray().Select(x => (ushort)x).ToArray(); } + + public bool TryGetTable(uint tag, out byte[] table) + { + table = null; + return false; + } } class HeadlessTextShaperStub : ITextShaperImpl @@ -136,7 +154,7 @@ namespace Avalonia.Headless class HeadlessFontManagerStub : IFontManagerImpl { - public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) + public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) { return new HeadlessGlyphTypefaceImpl(); } diff --git a/src/Skia/Avalonia.Skia/FontManagerImpl.cs b/src/Skia/Avalonia.Skia/FontManagerImpl.cs index 125dd0e455..6b5e0b3db5 100644 --- a/src/Skia/Avalonia.Skia/FontManagerImpl.cs +++ b/src/Skia/Avalonia.Skia/FontManagerImpl.cs @@ -102,7 +102,7 @@ namespace Avalonia.Skia return false; } - public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) + public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) { SKTypeface skTypeface = null; diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs index a3174010a1..7f3faf251f 100644 --- a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs @@ -1,14 +1,14 @@ using System; using System.Runtime.InteropServices; +using Avalonia.Media; using Avalonia.Metadata; -using Avalonia.Platform; using HarfBuzzSharp; using SkiaSharp; namespace Avalonia.Skia { [Unstable] - public class GlyphTypefaceImpl : IGlyphTypefaceImpl + public class GlyphTypefaceImpl : IGlyphTypeface { private bool _isDisposed; @@ -25,35 +25,30 @@ namespace Avalonia.Skia Font.SetFunctionsOpenType(); - DesignEmHeight = (short)Typeface.UnitsPerEm; - var metrics = Typeface.ToFont().Metrics; const double defaultFontRenderingEmSize = 12.0; - Ascent = (int)(metrics.Ascent / defaultFontRenderingEmSize * Typeface.UnitsPerEm); - - Descent = (int)(metrics.Descent / defaultFontRenderingEmSize * Typeface.UnitsPerEm); - - LineGap = (int)(metrics.Leading / defaultFontRenderingEmSize * Typeface.UnitsPerEm); - - UnderlinePosition = metrics.UnderlinePosition != null ? + Metrics = new FontMetrics + { + DesignEmHeight = (short)Typeface.UnitsPerEm, + Ascent = (int)(metrics.Ascent / defaultFontRenderingEmSize * Typeface.UnitsPerEm), + Descent = (int)(metrics.Descent / defaultFontRenderingEmSize * Typeface.UnitsPerEm), + LineGap = (int)(metrics.Leading / defaultFontRenderingEmSize * Typeface.UnitsPerEm), + UnderlinePosition = metrics.UnderlinePosition != null ? (int)(metrics.UnderlinePosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : - 0; - - UnderlineThickness = metrics.UnderlineThickness != null ? + 0, + UnderlineThickness = metrics.UnderlineThickness != null ? (int)(metrics.UnderlineThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : - 0; - - StrikethroughPosition = metrics.StrikeoutPosition != null ? + 0, + StrikethroughPosition = metrics.StrikeoutPosition != null ? (int)(metrics.StrikeoutPosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : - 0; - - StrikethroughThickness = metrics.StrikeoutThickness != null ? + 0, + StrikethroughThickness = metrics.StrikeoutThickness != null ? (int)(metrics.StrikeoutThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : - 0; - - IsFixedPitch = Typeface.IsFixedPitch; + 0, + IsFixedPitch = Typeface.IsFixedPitch + }; GlyphCount = Typeface.GlyphCount; @@ -69,41 +64,16 @@ namespace Avalonia.Skia public SKTypeface Typeface { get; } public int ReplacementCodepoint { get; } - - /// - public short DesignEmHeight { get; } - - /// - public int Ascent { get; } - - /// - public int Descent { get; } - - /// - public int LineGap { get; } - /// - public int UnderlinePosition { get; } - - /// - public int UnderlineThickness { get; } - - /// - public int StrikethroughPosition { get; } - - /// - public int StrikethroughThickness { get; } - - /// - public bool IsFixedPitch { get; } + public FontMetrics Metrics { get; } public int GlyphCount { get; } - + public bool IsFakeBold { get; } - + public bool IsFakeItalic { get; } - /// + /// public ushort GetGlyph(uint codepoint) { if (Font.TryGetGlyph(codepoint, out var glyph)) @@ -114,7 +84,14 @@ namespace Avalonia.Skia return 0; } - /// + public bool TryGetGlyph(uint codepoint, out ushort glyph) + { + glyph = GetGlyph(codepoint); + + return glyph != 0; + } + + /// public ushort[] GetGlyphs(ReadOnlySpan codepoints) { var glyphs = new ushort[codepoints.Length]; @@ -130,13 +107,13 @@ namespace Avalonia.Skia return glyphs; } - /// + /// public int GetGlyphAdvance(ushort glyph) { return Font.GetHorizontalGlyphAdvance(glyph); } - /// + /// public int[] GetGlyphAdvances(ReadOnlySpan glyphs) { var glyphIndices = new uint[glyphs.Length]; @@ -184,5 +161,10 @@ namespace Avalonia.Skia Dispose(true); GC.SuppressFinalize(this); } + + public bool TryGetTable(uint tag, out byte[] table) + { + return Typeface.TryGetTableData(tag, out table); + } } } diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index aa9225ba64..a9696efbd4 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -70,7 +70,7 @@ namespace Avalonia.Skia public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) { - if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface) + if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface) { throw new InvalidOperationException("PlatformImpl can't be null."); } @@ -244,13 +244,13 @@ namespace Avalonia.Skia "Current GPU acceleration backend does not support OpenGL integration"); } - public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) => new SKGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) => new SKHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) => new SKPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); private abstract class SKGlyphRunBufferBase : IGlyphRunBuffer @@ -258,11 +258,11 @@ namespace Avalonia.Skia protected readonly SKTextBlobBuilder _builder; protected readonly SKFont _font; - public SKGlyphRunBufferBase(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public SKGlyphRunBufferBase(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { _builder = new SKTextBlobBuilder(); - var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface.PlatformImpl; + var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface; _font = new SKFont { @@ -289,7 +289,7 @@ namespace Avalonia.Skia { private readonly SKRunBuffer _buffer; - public SKGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) + public SKGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) { _buffer = _builder.AllocateRun(_font, length, 0, 0); } @@ -301,7 +301,7 @@ namespace Avalonia.Skia { private readonly SKHorizontalRunBuffer _buffer; - public SKHorizontalGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) + public SKHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) { _buffer = _builder.AllocateHorizontalRun(_font, length, 0); } @@ -315,7 +315,7 @@ namespace Avalonia.Skia { private readonly SKPositionedRunBuffer _buffer; - public SKPositionedGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) + public SKPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) { _buffer = _builder.AllocatePositionedRun(_font, length); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs index c996337520..b98ed3ffe6 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs @@ -62,7 +62,7 @@ namespace Avalonia.Direct2D1.Media return false; } - public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) + public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) { return new GlyphTypefaceImpl(typeface); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs index 443bb8a207..77d0e58d3d 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs @@ -1,14 +1,15 @@ using System; +using System.Drawing.Drawing2D; using Avalonia.Media; using Avalonia.Metadata; -using Avalonia.Platform; using HarfBuzzSharp; using SharpDX.DirectWrite; +using FontMetrics = Avalonia.Media.FontMetrics; namespace Avalonia.Direct2D1.Media { [Unstable] - public class GlyphTypefaceImpl : IGlyphTypefaceImpl + public class GlyphTypefaceImpl : IGlyphTypeface { private bool _isDisposed; @@ -26,40 +27,28 @@ namespace Avalonia.Direct2D1.Media Font.GetScale(out var xScale, out _); - DesignEmHeight = (short)xScale; - if (!Font.TryGetHorizontalFontExtents(out var fontExtents)) { Font.TryGetVerticalFontExtents(out fontExtents); } - Ascent = -fontExtents.Ascender; - - Descent = -fontExtents.Descender; - - LineGap = fontExtents.LineGap; - - if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition)) - { - UnderlinePosition = underlinePosition; - } - - if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness)) - { - UnderlineThickness = underlineThickness; - } + Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition); + Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness); + Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition); + Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness); - if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition)) + Metrics = new FontMetrics { - StrikethroughPosition = strikethroughPosition; - } - - if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness)) - { - StrikethroughThickness = strikethroughThickness; - } - - IsFixedPitch = FontFace.IsMonospacedFont; + DesignEmHeight = (short)xScale, + Ascent = -fontExtents.Ascender, + Descent = -fontExtents.Descender, + LineGap = fontExtents.LineGap, + UnderlinePosition = underlinePosition, + UnderlineThickness = underlineThickness, + StrikethroughPosition = strikethroughPosition, + StrikethroughThickness = strikethroughThickness, + IsFixedPitch = FontFace.IsMonospacedFont + }; } private Blob GetTable(Face face, Tag tag) @@ -89,37 +78,11 @@ namespace Avalonia.Direct2D1.Media public HarfBuzzSharp.Font Font { get; } - /// - public short DesignEmHeight { get; } - - /// - public int Ascent { get; } - - /// - public int Descent { get; } - - /// - public int LineGap { get; } - - //ToDo: Read font table for these values - /// - public int UnderlinePosition { get; } - - /// - public int UnderlineThickness { get; } - - /// - public int StrikethroughPosition { get; } - - /// - public int StrikethroughThickness { get; } - - /// - public bool IsFixedPitch { get; } + public FontMetrics Metrics { get; } public int GlyphCount { get; set; } - /// + /// public ushort GetGlyph(uint codepoint) { if (Font.TryGetGlyph(codepoint, out var glyph)) @@ -130,7 +93,14 @@ namespace Avalonia.Direct2D1.Media return 0; } - /// + public bool TryGetGlyph(uint codepoint, out ushort glyph) + { + glyph = GetGlyph(codepoint); + + return glyph != 0; + } + + /// public ushort[] GetGlyphs(ReadOnlySpan codepoints) { var glyphs = new ushort[codepoints.Length]; @@ -146,13 +116,13 @@ namespace Avalonia.Direct2D1.Media return glyphs; } - /// + /// public int GetGlyphAdvance(ushort glyph) { return Font.GetHorizontalGlyphAdvance(glyph); } - /// + /// public int[] GetGlyphAdvances(ReadOnlySpan glyphs) { var glyphIndices = new uint[glyphs.Length]; @@ -189,6 +159,21 @@ namespace Avalonia.Direct2D1.Media Dispose(true); GC.SuppressFinalize(this); } + + public bool TryGetTable(uint tag, out byte[] table) + { + table = null; + var blob = Face.ReferenceTable(tag); + + if (blob.Length > 0) + { + table = blob.AsSpan().ToArray(); + + return true; + } + + return false; + } } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index 7d0cf05b0b..7998c95877 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs @@ -64,7 +64,7 @@ namespace Avalonia.Skia.UnitTests.Media return true; } - public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) + public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) { SKTypeface skTypeface; diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index 960c409058..a315158e1b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -237,7 +237,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var glyph = typeface.GlyphTypeface.GetGlyph('a'); var advance = typeface.GlyphTypeface.GetGlyphAdvance(glyph) * - (12.0 / typeface.GlyphTypeface.DesignEmHeight); + (12.0 / typeface.GlyphTypeface.Metrics.DesignEmHeight); var paragraphWidth = advance * numberOfCharactersPerLine; diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index e21986d3a2..d6da2c77c4 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -578,9 +578,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { var glyphTypeface = Typeface.Default.GlyphTypeface; - var emHeight = glyphTypeface.DesignEmHeight; + var emHeight = glyphTypeface.Metrics.DesignEmHeight; - var lineHeight = (glyphTypeface.Descent - glyphTypeface.Ascent) * (12.0 / emHeight); + var lineHeight = glyphTypeface.Metrics.LineSpacing * (12.0 / emHeight); var layout = new TextLayout( text, diff --git a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs index 9f958bdcdd..2b1685f358 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs @@ -58,7 +58,7 @@ namespace Avalonia.UnitTests return false; } - public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) + public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) { var fontFamily = typeface.FontFamily; diff --git a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs index 04f316e5b4..3bcbc2efbd 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs @@ -1,11 +1,11 @@ using System; using System.IO; -using Avalonia.Platform; +using Avalonia.Media; using HarfBuzzSharp; namespace Avalonia.UnitTests { - public class HarfBuzzGlyphTypefaceImpl : IGlyphTypefaceImpl + public class HarfBuzzGlyphTypefaceImpl : IGlyphTypeface { private bool _isDisposed; private Blob _blob; @@ -21,28 +21,28 @@ namespace Avalonia.UnitTests Font.SetFunctionsOpenType(); Font.GetScale(out var scale, out _); - - DesignEmHeight = (short)scale; - - var metrics = Font.OpenTypeMetrics; const double defaultFontRenderingEmSize = 12.0; - Ascent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalAscender) / defaultFontRenderingEmSize * DesignEmHeight); - - Descent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalDescender) / defaultFontRenderingEmSize * DesignEmHeight); + var metrics = Font.OpenTypeMetrics; - LineGap = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalLineGap) / defaultFontRenderingEmSize * DesignEmHeight); + Metrics = new FontMetrics + { + DesignEmHeight = (short)scale, + Ascent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalAscender) / defaultFontRenderingEmSize * scale), + Descent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalDescender) / defaultFontRenderingEmSize * scale), + LineGap = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalLineGap) / defaultFontRenderingEmSize * scale), - UnderlinePosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineOffset) / defaultFontRenderingEmSize * DesignEmHeight); + UnderlinePosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineOffset) / defaultFontRenderingEmSize * scale), - UnderlineThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineSize) / defaultFontRenderingEmSize * DesignEmHeight); + UnderlineThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineSize) / defaultFontRenderingEmSize * scale), - StrikethroughPosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutOffset) / defaultFontRenderingEmSize * DesignEmHeight); + StrikethroughPosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutOffset) / defaultFontRenderingEmSize * scale), - StrikethroughThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutSize) / defaultFontRenderingEmSize * DesignEmHeight); + StrikethroughThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutSize) / defaultFontRenderingEmSize * scale), - IsFixedPitch = GetGlyphAdvance(GetGlyph('a')) == GetGlyphAdvance(GetGlyph('b')); + IsFixedPitch = GetGlyphAdvance(GetGlyph('a')) == GetGlyphAdvance(GetGlyph('b')) + }; GlyphCount = Face.GlyphCount; @@ -51,44 +51,19 @@ namespace Avalonia.UnitTests IsFakeItalic = isFakeItalic; } + public FontMetrics Metrics { get; } + public Face Face { get; } public Font Font { get; } - /// - public short DesignEmHeight { get; } - - /// - public int Ascent { get; } - - /// - public int Descent { get; } - - /// - public int LineGap { get; } - - /// - public int UnderlinePosition { get; } - - /// - public int UnderlineThickness { get; } - - /// - public int StrikethroughPosition { get; } - - /// - public int StrikethroughThickness { get; } - - /// - public bool IsFixedPitch { get; } - public int GlyphCount { get; set; } public bool IsFakeBold { get; } public bool IsFakeItalic { get; } - /// + /// public ushort GetGlyph(uint codepoint) { if (Font.TryGetGlyph(codepoint, out var glyph)) @@ -99,7 +74,21 @@ namespace Avalonia.UnitTests return 0; } - /// + public bool TryGetGlyph(uint codepoint,out ushort glyph) + { + glyph = 0; + + if (Font.TryGetGlyph(codepoint, out var glyphId)) + { + glyph = (ushort)glyphId; + + return true; + } + + return false; + } + + /// public ushort[] GetGlyphs(ReadOnlySpan codepoints) { var glyphs = new ushort[codepoints.Length]; @@ -115,13 +104,13 @@ namespace Avalonia.UnitTests return glyphs; } - /// + /// public int GetGlyphAdvance(ushort glyph) { return Font.GetHorizontalGlyphAdvance(glyph); } - /// + /// public int[] GetGlyphAdvances(ReadOnlySpan glyphs) { var glyphIndices = new uint[glyphs.Length]; @@ -134,6 +123,21 @@ namespace Avalonia.UnitTests return Font.GetHorizontalGlyphAdvances(glyphIndices); } + public bool TryGetTable(uint tag, out byte[] table) + { + table = null; + var blob = Face.ReferenceTable(tag); + + if (blob.Length > 0) + { + table = blob.AsSpan().ToArray(); + + return true; + } + + return false; + } + private void Dispose(bool disposing) { if (_isDisposed) diff --git a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs index e2678394df..e9b923a367 100644 --- a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs +++ b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs @@ -33,7 +33,7 @@ namespace Avalonia.UnitTests return false; } - public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) + public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) { return new MockGlyphTypeface(); } diff --git a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs index 0b7beeb4fb..a1c492a7f1 100644 --- a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs +++ b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs @@ -1,19 +1,18 @@ using System; -using Avalonia.Platform; +using Avalonia.Media; namespace Avalonia.UnitTests { - public class MockGlyphTypeface : IGlyphTypefaceImpl + public class MockGlyphTypeface : IGlyphTypeface { - public short DesignEmHeight => 10; - public int Ascent => 2; - public int Descent => 10; - public int LineGap { get; } - public int UnderlinePosition { get; } - public int UnderlineThickness { get; } - public int StrikethroughPosition { get; } - public int StrikethroughThickness { get; } - public bool IsFixedPitch { get; } + public FontMetrics Metrics => new FontMetrics + { + DesignEmHeight = 10, + Ascent = 2, + Descent = 10, + IsFixedPitch = true + }; + public int GlyphCount => 1337; public ushort GetGlyph(uint codepoint) @@ -31,6 +30,13 @@ namespace Avalonia.UnitTests return 8; } + public bool TryGetGlyph(uint codepoint, out ushort glyph) + { + glyph = 8; + + return true; + } + public int[] GetGlyphAdvances(ReadOnlySpan glyphs) { var advances = new int[glyphs.Length]; @@ -44,5 +50,11 @@ namespace Avalonia.UnitTests } public void Dispose() { } + + public bool TryGetTable(uint tag, out byte[] table) + { + table = null; + return false; + } } } From a4a24c2fa56c54753102df216acb76ff0e285c88 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 12 Oct 2022 15:41:13 +0200 Subject: [PATCH 21/53] Remove GlyphTypeface.PlatformImpl --- src/Avalonia.Base/Media/FontManager.cs | 2 +- .../HeadlessPlatformRenderInterface.cs | 6 +++--- src/Skia/Avalonia.Skia/TextShaperImpl.cs | 2 +- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 16 ++++++++-------- .../Avalonia.Direct2D1/Media/TextShaperImpl.cs | 2 +- .../Media/GlyphRunTests.cs | 2 +- .../VisualTree/MockRenderInterface.cs | 6 +++--- .../Avalonia.Benchmarks/NullRenderingPlatform.cs | 6 +++--- .../Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs | 2 +- .../MockPlatformRenderInterface.cs | 6 +++--- 10 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs index dcb29ffebf..d92d003c2a 100644 --- a/src/Avalonia.Base/Media/FontManager.cs +++ b/src/Avalonia.Base/Media/FontManager.cs @@ -81,7 +81,7 @@ namespace Avalonia.Media PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates); /// - /// Returns a new , or an existing one if a matching exists. + /// Returns a new , or an existing one if a matching exists. /// /// The typeface. /// diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 12a9a016f3..fcd7f1e31f 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -115,17 +115,17 @@ namespace Avalonia.Headless return new HeadlessGeometryStub(new Rect(glyphRun.Size)); } - public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return new HeadlessGlyphRunBufferStub(); } - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return new HeadlessHorizontalGlyphRunBufferStub(); } - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return new HeadlessPositionedGlyphRunBufferStub(); } diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index 6da8a20f6a..b07deb1f4d 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -31,7 +31,7 @@ namespace Avalonia.Skia buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); - var font = ((GlyphTypefaceImpl)typeface.PlatformImpl).Font; + var font = ((GlyphTypefaceImpl)typeface).Font; font.Shape(buffer); diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 45744e6efa..8103f89dad 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -162,7 +162,7 @@ namespace Avalonia.Direct2D1 public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) { - if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface) + if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface) { throw new InvalidOperationException("PlatformImpl can't be null."); } @@ -264,9 +264,9 @@ namespace Avalonia.Direct2D1 { protected readonly SharpDX.DirectWrite.GlyphRun _dwRun; - public DWGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public DWGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { - var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface.PlatformImpl; + var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface; _dwRun = new SharpDX.DirectWrite.GlyphRun { @@ -286,7 +286,7 @@ namespace Avalonia.Direct2D1 private class DWHorizontalGlyphRunBuffer : DWGlyphRunBuffer, IHorizontalGlyphRunBuffer { - public DWHorizontalGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public DWHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) { _dwRun.Advances = new float[length]; @@ -297,7 +297,7 @@ namespace Avalonia.Direct2D1 private class DWPositionedGlyphRunBuffer : DWGlyphRunBuffer, IPositionedGlyphRunBuffer { - public DWPositionedGlyphRunBuffer(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public DWPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) { _dwRun.Advances = new float[length]; @@ -307,17 +307,17 @@ namespace Avalonia.Direct2D1 public Span GlyphPositions => MemoryMarshal.Cast(_dwRun.Offsets.AsSpan()); } - public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return new DWGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); } - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return new DWHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); } - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return new DWPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs index bda36e6c8c..064320f809 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs @@ -31,7 +31,7 @@ namespace Avalonia.Direct2D1.Media buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); - var font = ((GlyphTypefaceImpl)typeface.PlatformImpl).Font; + var font = ((GlyphTypefaceImpl)typeface).Font; font.Shape(buffer); diff --git a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs index cd0586233f..df16c1b34f 100644 --- a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs @@ -185,7 +185,7 @@ namespace Avalonia.Base.UnitTests.Media var characters = new ReadOnlySlice(Enumerable.Repeat('a', count).ToArray(), start, count); - return new GlyphRun(new GlyphTypeface(new MockGlyphTypeface()), 10, characters, glyphIndices, glyphAdvances, + return new GlyphRun(new MockGlyphTypeface(), 10, characters, glyphIndices, glyphAdvances, glyphClusters: glyphClusters, biDiLevel: bidiLevel); } } diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs index d21879f349..a7a55653b7 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs @@ -126,17 +126,17 @@ namespace Avalonia.Base.UnitTests.VisualTree throw new NotImplementedException(); } - public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { throw new NotImplementedException(); } - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { throw new NotImplementedException(); } - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index a2c2936a4d..34f0dfef11 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -117,17 +117,17 @@ namespace Avalonia.Benchmarks return new MockStreamGeometryImpl(); } - public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { throw new NotImplementedException(); } - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { throw new NotImplementedException(); } - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs index 459ae3a549..7b7488bd5a 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs @@ -30,7 +30,7 @@ namespace Avalonia.UnitTests buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); - var font = ((HarfBuzzGlyphTypefaceImpl)typeface.PlatformImpl).Font; + var font = ((HarfBuzzGlyphTypefaceImpl)typeface).Font; font.Shape(buffer); diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index 314cc95041..586436ef7f 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -152,17 +152,17 @@ namespace Avalonia.UnitTests return Mock.Of(); } - public IGlyphRunBuffer AllocateGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return Mock.Of(); } - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return Mock.Of(); } - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(GlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { return Mock.Of(); } From 97c0b1304892401ec9291b6c7bf675b0c2d3dd0e Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 12 Oct 2022 13:56:07 +0000 Subject: [PATCH 22/53] fix android crash when ime is dismissed on a read only input --- .../Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index f8eaeba897..8e47d30bcb 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -301,7 +301,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform IsComposing = true; - _inputMethod.Client.SetPreeditText(ComposingText); + _inputMethod.Client?.SetPreeditText(ComposingText); return base.SetComposingText(text, newCursorPosition); } From cd1c62b1b4fd1a43ec31d2e4afdc3713807b59fd Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 12 Oct 2022 16:03:06 +0200 Subject: [PATCH 23/53] fix: Diasable Net Analyzers to avoid submodule commit issue --- src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index 22c410198f..74debed828 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -9,6 +9,11 @@ NU1605;CS8632 + + + false + + Shared/AvaloniaResourcesIndex.cs From 591a0794fe43b8ac6dc9dd70b9745d1c3e792906 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 12 Oct 2022 16:39:14 +0200 Subject: [PATCH 24/53] feat: Enable 1825 --- .editorconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.editorconfig b/.editorconfig index 179491eece..a3f524f8b0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -137,6 +137,9 @@ space_within_single_line_array_initializer_braces = true #Net Analyzer dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed. +#CA1825: Avoid zero-length array allocations +dotnet_diagnostic.CA1825.severity = warning + # Wrapping preferences csharp_wrap_before_ternary_opsigns = false From 543acb1235c6dbed4a7b612b7878214668005f1e Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 12 Oct 2022 16:39:38 +0200 Subject: [PATCH 25/53] fix: Address CA1825 rule --- .../Media/TextFormatting/Unicode/GraphemeBreak.cs | 2 +- .../Helpers/ColorPickerHelpers.cs | 2 +- .../Remote/HtmlTransport/SimpleWebSocketHttpServer.cs | 2 +- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 6 +++--- src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs index 6986b908a1..d0ceba62f4 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs @@ -2,6 +2,6 @@ namespace Avalonia.Media.TextFormatting.Unicode { internal static class GraphemeBreak { - public static byte[] Data => new byte[0]; + public static byte[] Data => System.Array.Empty(); } } diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index c1904a3c30..381bc42aaa 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -49,7 +49,7 @@ namespace Avalonia.Controls.Primitives { if (width == 0 || height == 0) { - return new byte[0]; + return Array.Empty(); } var bitmap = await Task.Run(() => diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs index 6af21bbcf5..9a872df960 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs @@ -140,7 +140,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport IsWebsocketRequest = true; if (headers.TryGetValue("Sec-WebSocket-Protocol", out h)) WebSocketProtocols = h.Split(',').Select(x => x.Trim()).ToArray(); - else WebSocketProtocols = new string[0]; + else WebSocketProtocols = Array.Empty(); } } diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index c0511420a6..657e324010 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -340,7 +340,7 @@ namespace Avalonia.FreeDesktop { var id = item == null ? 0 : GetId(item); var props = GetProperties((item, menu), propertyNames); - var children = (depth == 0 || menu == null) ? new object[0] : new object[menu.Items.Count]; + var children = (depth == 0 || menu == null) ? Array.Empty() : new object[menu.Items.Count]; if(menu != null) for (var c = 0; c < children.Length; c++) { @@ -397,7 +397,7 @@ namespace Avalonia.FreeDesktop { foreach (var e in Events) HandleEvent(e.id, e.eventId, e.data, e.timestamp); - return Task.FromResult(new int[0]); + return Task.FromResult(Array.Empty()); } public async Task AboutToShowAsync(int Id) @@ -407,7 +407,7 @@ namespace Avalonia.FreeDesktop public async Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids) { - return (new int[0], new int[0]); + return (Array.Empty(), Array.Empty()); } #region Events diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index ce210019c0..5c7ec2bbd2 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -99,7 +99,7 @@ namespace Avalonia.LinuxFramebuffer.Output // prepare for the new ioctl call var handles = new uint[] {handle, 0, 0, 0}; var pitches = new uint[] {stride, 0, 0, 0}; - var offsets = new uint[] {}; + var offsets = Array.Empty(); var ret = drmModeAddFB2(_card.Fd, w, h, format, handles, pitches, offsets, out var fbHandle, 0); From 85ac29c788305238e7dd097971bcf71befd8c38e Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 12 Oct 2022 14:58:59 +0200 Subject: [PATCH 26/53] fix: CA1802 --- .editorconfig | 3 +++ src/Windows/Avalonia.Win32/Win32Platform.cs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index 179491eece..917bd21bfe 100644 --- a/.editorconfig +++ b/.editorconfig @@ -137,6 +137,9 @@ space_within_single_line_array_initializer_braces = true #Net Analyzer dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed. +# CA1802: Use literals where appropriate +dotnet_diagnostic.CA1802.severity = warning + # Wrapping preferences csharp_wrap_before_ternary_opsigns = false diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 29b4baeeb0..3cdc3586dc 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -241,8 +241,8 @@ namespace Avalonia.Win32 }); } - private static readonly int SignalW = unchecked((int) 0xdeadbeaf); - private static readonly int SignalL = unchecked((int)0x12345678); + private const int SignalW = unchecked((int)0xdeadbeaf); + private const int SignalL = unchecked((int)0x12345678); public void Signal(DispatcherPriority prio) { From dd7e941c3773fb43608a49ab3655bac52b0a06d0 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Thu, 13 Oct 2022 00:13:27 +0800 Subject: [PATCH 27/53] feat: remove header from DatePicker. --- .../Pages/DateTimePickerPage.xaml | 8 ++--- .../DateTimePickers/DatePicker.cs | 30 ------------------- .../Accents/FluentControlResourcesDark.xaml | 2 -- .../Accents/FluentControlResourcesLight.xaml | 2 -- .../Controls/DatePicker.xaml | 13 +------- .../Controls/DatePicker.xaml | 13 +------- 6 files changed, 6 insertions(+), 62 deletions(-) diff --git a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml index 29cd939520..8b6b7f6c16 100644 --- a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml +++ b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml @@ -13,17 +13,17 @@ Margin="16" HorizontalAlignment="Stretch" Spacing="16"> - A simple DatePicker with a header + A simple DatePicker - + - <DatePicker Header="Pick a date" /> + <DatePicker/> @@ -33,7 +33,7 @@ - + diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index fb27feb521..bb05cd1b1f 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -40,18 +40,6 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(DayVisible), x => x.DayVisible, (x, v) => x.DayVisible = v); - /// - /// Defines the Property - /// - public static readonly StyledProperty HeaderProperty = - AvaloniaProperty.Register(nameof(Header)); - - /// - /// Defines the Property - /// - public static readonly StyledProperty HeaderTemplateProperty = - AvaloniaProperty.Register(nameof(HeaderTemplate)); - /// /// Defines the Property /// @@ -152,24 +140,6 @@ namespace Avalonia.Controls } } - /// - /// Gets or sets the DatePicker header - /// - public object Header - { - get => GetValue(HeaderProperty); - set => SetValue(HeaderProperty, value); - } - - /// - /// Gets or sets the header template - /// - public IDataTemplate HeaderTemplate - { - get => GetValue(HeaderTemplateProperty); - set => SetValue(HeaderTemplateProperty, value); - } - /// /// Gets or sets the maximum year for the picker /// diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index 88b9f18eee..785766214c 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -424,7 +424,6 @@ - @@ -595,7 +594,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index b51a0bf259..9a9a7f93c6 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -420,7 +420,6 @@ - @@ -589,7 +588,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml index 6f1e4fccfe..1585dada90 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml @@ -28,7 +28,6 @@ - 0,0,0,4 40 40 41 @@ -84,18 +83,8 @@ - - - +