From 94ecd84c8f24d210f537c495e044ded266f1147b Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 18 Jul 2022 11:25:13 +0200 Subject: [PATCH 01/59] feat(LibInputBackend): minimal linux boot with no input --- .../Input/LibInput/LibInputBackend.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs index 15d42789d4..6e26794b24 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs @@ -1,16 +1,15 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.IO; using System.Threading; using Avalonia.Input; using Avalonia.Input.Raw; -using Avalonia.Threading; using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods; namespace Avalonia.LinuxFramebuffer.Input.LibInput { public class LibInputBackend : IInputBackend { + private const string LibInput = nameof(Logging.LogArea.X11Platform) + "/" + nameof(LibInput); private IScreenInfoProvider _screen; private IInputRoot _inputRoot; private readonly Queue _inputThreadActions = new Queue(); @@ -29,15 +28,21 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput new Thread(()=>InputThread(ctx)).Start(); } - - private unsafe void InputThread(IntPtr ctx) { var fd = libinput_get_fd(ctx); var timeval = stackalloc IntPtr[2]; - + if (!Directory.Exists("/dev/input")) + { + if (Logging.Logger.IsEnabled(Logging.LogEventLevel.Warning,LibInput)) + { + Logging.Logger.TryGet(Logging.LogEventLevel.Warning, LibInput) + ?.Log(this, "Not connect any input device."); + } + return; + } foreach (var f in Directory.GetFiles("/dev/input", "event*")) libinput_path_add_device(ctx, f); while (true) From ef7ecb7c065719349f9c38b29001c29005f4d86f Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 2 Aug 2022 10:35:53 +0200 Subject: [PATCH 02/59] Revert "feat(LibInputBackend): minimal linux boot with no input" This reverts commit 94ecd84c8f24d210f537c495e044ded266f1147b. --- .../Input/LibInput/LibInputBackend.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs index 6e26794b24..15d42789d4 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs @@ -1,15 +1,16 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Threading; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Threading; using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods; namespace Avalonia.LinuxFramebuffer.Input.LibInput { public class LibInputBackend : IInputBackend { - private const string LibInput = nameof(Logging.LogArea.X11Platform) + "/" + nameof(LibInput); private IScreenInfoProvider _screen; private IInputRoot _inputRoot; private readonly Queue _inputThreadActions = new Queue(); @@ -28,21 +29,15 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput new Thread(()=>InputThread(ctx)).Start(); } + + private unsafe void InputThread(IntPtr ctx) { var fd = libinput_get_fd(ctx); var timeval = stackalloc IntPtr[2]; - if (!Directory.Exists("/dev/input")) - { - if (Logging.Logger.IsEnabled(Logging.LogEventLevel.Warning,LibInput)) - { - Logging.Logger.TryGet(Logging.LogEventLevel.Warning, LibInput) - ?.Log(this, "Not connect any input device."); - } - return; - } + foreach (var f in Directory.GetFiles("/dev/input", "event*")) libinput_path_add_device(ctx, f); while (true) From 197f514ae7fbb94849ce048c28434de4f1f2aee0 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 2 Aug 2022 10:47:11 +0200 Subject: [PATCH 03/59] feat(X11): NullInputBackend --- .../Input/LibInput/LibInputBackend.cs | 6 ------ .../Input/NullInput/NullInputBackend.cs | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs index 15d42789d4..702ae3f8e5 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.IO; using System.Threading; using Avalonia.Input; using Avalonia.Input.Raw; -using Avalonia.Threading; using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods; namespace Avalonia.LinuxFramebuffer.Input.LibInput { @@ -29,8 +27,6 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput new Thread(()=>InputThread(ctx)).Start(); } - - private unsafe void InputThread(IntPtr ctx) { var fd = libinput_get_fd(ctx); @@ -143,8 +139,6 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput } } - - public void Initialize(IScreenInfoProvider screen, Action onInput) { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs new file mode 100644 index 0000000000..551c0995a2 --- /dev/null +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs @@ -0,0 +1,17 @@ +using System; +using Avalonia.Input; +using Avalonia.Input.Raw; + +namespace Avalonia.LinuxFramebuffer.Input.NullInput; + +internal class NullInputBackend : IInputBackend +{ + + public void Initialize(IScreenInfoProvider screen, Action onInput) + { + } + + public void SetInputRoot(IInputRoot root) + { + } +} From 46185b6a6eb78a887567dc483c5a1351fab61e30 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 2 Aug 2022 10:59:33 +0200 Subject: [PATCH 04/59] feat: add overload thath accept IInputBackend to Linux AppBuilder --- .../LinuxFramebufferPlatform.cs | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index a642766809..c819407cc9 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -15,7 +15,6 @@ using Avalonia.LinuxFramebuffer.Output; using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; -using Avalonia.Threading; using JetBrains.Annotations; namespace Avalonia.LinuxFramebuffer @@ -37,9 +36,9 @@ namespace Avalonia.LinuxFramebuffer Threading = new InternalPlatformThreadingInterface(); if (_fb is IGlOutputBackend gl) AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl.PlatformOpenGlInterface); - + var opts = AvaloniaLocator.Current.GetService() ?? new LinuxFramebufferPlatformOptions(); - + AvaloniaLocator.CurrentMutable .Bind().ToConstant(Threading) .Bind().ToConstant(new DefaultRenderTimer(opts.Fps)) @@ -50,12 +49,12 @@ namespace Avalonia.LinuxFramebuffer .Bind().ToSingleton(); } - - internal static LinuxFramebufferLifetime Initialize(T builder, IOutputBackend outputBackend) where T : AppBuilderBase, new() + + internal static LinuxFramebufferLifetime Initialize(T builder, IOutputBackend outputBackend, IInputBackend inputBackend) where T : AppBuilderBase, new() { var platform = new LinuxFramebufferPlatform(outputBackend); builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev"); - return new LinuxFramebufferLifetime(platform._fb); + return new LinuxFramebufferLifetime(platform._fb, inputBackend); } } @@ -71,13 +70,13 @@ namespace Avalonia.LinuxFramebuffer { _fb = fb; } - + public LinuxFramebufferLifetime(IOutputBackend fb, IInputBackend input) { _fb = fb; _inputBackend = input; } - + public Control MainView { get => (Control)_topLevel?.Content; @@ -117,7 +116,7 @@ namespace Avalonia.LinuxFramebuffer { Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args)); } - + public void Shutdown(int exitCode) { ExitCode = exitCode; @@ -131,22 +130,22 @@ namespace Avalonia.LinuxFramebuffer public static class LinuxFramebufferPlatformExtensions { - public static int StartLinuxFbDev(this T builder, string[] args, string fbdev = null, double scaling = 1) + public static int StartLinuxFbDev(this T builder, string[] args, string fbdev = null, double scaling = 1, IInputBackend inputBackend = default) where T : AppBuilderBase, new() => - StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling }); - public static int StartLinuxFbDev(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling) + StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling }, inputBackend); + public static int StartLinuxFbDev(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling, IInputBackend inputBackend = default) where T : AppBuilderBase, new() => - StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling }); - - public static int StartLinuxDrm(this T builder, string[] args, string card = null, double scaling = 1) - where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card) {Scaling = scaling}); - public static int StartLinuxDrm(this T builder, string[] args, string card = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null) - where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options)); - - public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend backend) + StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling }, inputBackend); + + public static int StartLinuxDrm(this T builder, string[] args, string card = null, double scaling = 1, IInputBackend inputBackend = default) + where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card) { Scaling = scaling }, inputBackend); + public static int StartLinuxDrm(this T builder, string[] args, string card = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null, IInputBackend inputBackend = default) + where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options), inputBackend); + + public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend outputBackend, IInputBackend inputBackend = default) where T : AppBuilderBase, new() { - var lifetime = LinuxFramebufferPlatform.Initialize(builder, backend); + var lifetime = LinuxFramebufferPlatform.Initialize(builder, outputBackend, inputBackend); builder.SetupWithLifetime(lifetime); lifetime.Start(args); builder.Instance.Run(lifetime.Token); From 64c94ca3415e3c5f9961c62eb6ca090ee7ab5c0a Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 3 Aug 2022 09:24:17 +0200 Subject: [PATCH 05/59] fix: NullInputBackend visibility --- .../Input/NullInput/NullInputBackend.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs index 551c0995a2..05dd0195b1 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs @@ -4,9 +4,8 @@ using Avalonia.Input.Raw; namespace Avalonia.LinuxFramebuffer.Input.NullInput; -internal class NullInputBackend : IInputBackend +public class NullInputBackend : IInputBackend { - public void Initialize(IScreenInfoProvider screen, Action onInput) { } From 282071751d9ebfdbf66c3be61851f23e54fae8dc Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 19 Aug 2022 18:05:42 +0200 Subject: [PATCH 06/59] fix: CS0169 Never used field --- src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs | 1 - src/Avalonia.X11/NativeDialogs/Gtk.cs | 3 --- .../Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs | 1 - 3 files changed, 5 deletions(-) diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index dc2b2cd7cc..79cf909e55 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -129,7 +129,6 @@ namespace Avalonia.Controls private bool _clipValueToMinMax; private bool _isSyncingTextAndValueProperties; private bool _isTextChangedFromUI; - private CultureInfo? _cultureInfo; private NumberStyles _parsingNumberStyle = NumberStyles.Any; private NumberFormatInfo? _numberFormat; diff --git a/src/Avalonia.X11/NativeDialogs/Gtk.cs b/src/Avalonia.X11/NativeDialogs/Gtk.cs index ae04c072a5..d5eae037a9 100644 --- a/src/Avalonia.X11/NativeDialogs/Gtk.cs +++ b/src/Avalonia.X11/NativeDialogs/Gtk.cs @@ -258,9 +258,6 @@ namespace Avalonia.X11.NativeDialogs public static IntPtr GetForeignWindow(IntPtr xid) => gdk_x11_window_foreign_new_for_display(s_display, xid); - static object s_startGtkLock = new(); - static Task s_startGtkTask; - public static Task StartGtk() { return StartGtkCore(); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs index 2eb10ae666..1e3c4bed48 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs @@ -13,7 +13,6 @@ namespace Avalonia.LinuxFramebuffer.Input.EvDev private readonly EvDevDeviceDescription[] _deviceDescriptions; private readonly List _handlers = new List(); private int _epoll; - private bool _isQueueHandlerTriggered; private object _lock = new object(); private Action _onInput; private IInputRoot _inputRoot; From 404bb8745ee834d11d5a30cada7428c78d810bab Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 19 Aug 2022 18:09:41 +0200 Subject: [PATCH 07/59] fix: Warning CS0168 The variable 'e' is declared but never used --- tests/Avalonia.IntegrationTests.Appium/WindowTests.cs | 2 +- tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 382306ac83..9cce169744 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -230,7 +230,7 @@ namespace Avalonia.IntegrationTests.Appium PixelRect.Parse(_session.FindElementByAccessibilityId("ScreenRect").Text), double.Parse(_session.FindElementByAccessibilityId("Scaling").Text)); } - catch (OpenQA.Selenium.NoSuchElementException e) when (retry++ < 3) + catch (OpenQA.Selenium.NoSuchElementException) when (retry++ < 3) { // MacOS sometimes seems to need a bit of time to get itself back in order after switching out // of fullscreen. diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 4e5344dd25..facb25c18e 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -30,7 +30,7 @@ namespace Avalonia.IntegrationTests.Appium tab.Click(); return; } - catch (WebDriverException e) when (retry++ < 3) + catch (WebDriverException) when (retry++ < 3) { // MacOS sometimes seems to need a bit of time to get itself back in order after switching out // of fullscreen. From fab2fe7aa0dcc4c36bc6d9a2a2b0895015d20d63 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 19 Aug 2022 18:12:44 +0200 Subject: [PATCH 08/59] fix: Warning CS0414 The field is assigned but its value is never used --- src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs | 3 --- src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index 07db6013c7..338a3420fa 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs @@ -42,7 +42,6 @@ namespace Avalonia.Web.Blazor private GRGlInterface? _glInterface; private const SKColorType ColorType = SKColorType.Rgba8888; - private bool _initialised; private bool _useGL; private bool _inputElementFocused; @@ -300,8 +299,6 @@ namespace Avalonia.Web.Blazor _interop.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); - _initialised = true; - Threading.Dispatcher.UIThread.Post(async () => { _interop.RequestAnimationFrame(true); diff --git a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs index 3adefd965f..92e2b0689e 100644 --- a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs +++ b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs @@ -17,7 +17,6 @@ namespace Avalonia.Win32.Input private bool _active; private bool _showCompositionWindow; private Imm32CaretManager _caretManager = new(); - private bool _showCandidateList; private ushort _langId; private const int _caretMargin = 1; @@ -32,7 +31,6 @@ namespace Avalonia.Win32.Input _active = false; _langId = PRIMARYLANGID(LGID(HKL)); _showCompositionWindow = true; - _showCandidateList = true; IsComposing = false; } @@ -50,7 +48,6 @@ namespace Avalonia.Win32.Input _active = false; _langId = 0; _showCompositionWindow = false; - _showCandidateList = false; IsComposing = false; } From 011cb37712f897f11113383eb85681e879d92ba4 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 20 Aug 2022 22:05:40 -0400 Subject: [PATCH 09/59] Update DatePicker template part names --- .../DateTimePickers/DatePicker.cs | 18 +++++++++--------- .../Controls/DatePicker.xaml | 18 +++++++++--------- .../Controls/DatePicker.xaml | 18 +++++++++--------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index f2b808fe0d..4b8fbaec14 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -14,15 +14,15 @@ namespace Avalonia.Controls /// /// A control to allow the user to select a date /// - [TemplatePart("ButtonContentGrid", typeof(Grid))] - [TemplatePart("DayText", typeof(TextBlock))] - [TemplatePart("FirstSpacer", typeof(Rectangle))] - [TemplatePart("FlyoutButton", typeof(Button))] - [TemplatePart("MonthText", typeof(TextBlock))] - [TemplatePart("PickerPresenter", typeof(DatePickerPresenter))] - [TemplatePart("Popup", typeof(Popup))] - [TemplatePart("SecondSpacer", typeof(Rectangle))] - [TemplatePart("YearText", typeof(TextBlock))] + [TemplatePart("PART_ButtonContentGrid", typeof(Grid))] + [TemplatePart("PART_DayText", typeof(TextBlock))] + [TemplatePart("PART_FirstSpacer", typeof(Rectangle))] + [TemplatePart("PART_FlyoutButton", typeof(Button))] + [TemplatePart("PART_MonthText", typeof(TextBlock))] + [TemplatePart("PART_PickerPresenter", typeof(DatePickerPresenter))] + [TemplatePart("PART_Popup", typeof(Popup))] + [TemplatePart("PART_SecondSpacer", typeof(Rectangle))] + [TemplatePart("PART_YearText", typeof(TextBlock))] [PseudoClasses(":hasnodate")] public class DatePicker : TemplatedControl { diff --git a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml index bc47f3892e..c5afa3dfca 100644 --- a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml @@ -101,7 +101,7 @@ HorizontalAlignment="Stretch" VerticalAlignment="Top"/> - - - + diff --git a/src/Avalonia.Themes.Simple/Controls/DatePicker.xaml b/src/Avalonia.Themes.Simple/Controls/DatePicker.xaml index a6da2ad9f8..f7310eba64 100644 --- a/src/Avalonia.Themes.Simple/Controls/DatePicker.xaml +++ b/src/Avalonia.Themes.Simple/Controls/DatePicker.xaml @@ -102,7 +102,7 @@ Content="{TemplateBinding Header}" ContentTemplate="{TemplateBinding HeaderTemplate}" /> - - - + From 66baf9c03835617f0e10dc8581bf870dfc872303 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 20 Aug 2022 22:47:54 -0400 Subject: [PATCH 10/59] Update Date/TimePicker template part names --- .../DateTimePickers/DatePicker.cs | 18 ++--- .../DateTimePickers/DatePickerPresenter.cs | 68 +++++++++---------- .../DateTimePickers/TimePicker.cs | 48 ++++++------- .../DateTimePickers/TimePickerPresenter.cs | 56 +++++++-------- .../Controls/DatePicker.xaml | 38 +++++------ .../Controls/TimePicker.xaml | 60 ++++++++-------- .../Controls/DatePicker.xaml | 34 +++++----- .../Controls/TimePicker.xaml | 58 ++++++++-------- 8 files changed, 190 insertions(+), 190 deletions(-) diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index 4b8fbaec14..db59dbdb4a 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -280,15 +280,15 @@ namespace Avalonia.Controls } base.OnApplyTemplate(e); - _flyoutButton = e.NameScope.Find - - - + @@ -216,42 +216,42 @@ Padding="{DynamicResource DateTimeFlyoutBorderPadding}" MaxHeight="398"> - + - + - - - + + - + - - - + + - + - - - + + - - - - - - - + @@ -214,50 +214,50 @@ CornerRadius="{TemplateBinding CornerRadius}"> - + - - - - - - - - - - - - @@ -272,12 +272,12 @@ Color="{DynamicResource ThemeAccentColor}" /> - - - - - - + + + + + diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs index 001f186761..43875dd990 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs @@ -1,21 +1,32 @@ using System; +using System.Runtime.InteropServices; using Avalonia; using Avalonia.Controls; -using Avalonia.Interactivity; using Avalonia.Markup.Xaml; -using Avalonia.Rendering; +using Avalonia.Threading; namespace IntegrationTestApp { public class ShowWindowTest : Window { + private readonly DispatcherTimer? _timer; + private readonly TextBox? _orderTextBox; + public ShowWindowTest() { InitializeComponent(); DataContext = this; PositionChanged += (s, e) => this.GetControl("Position").Text = $"{Position}"; - } + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + _orderTextBox = this.GetControl("Order"); + _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) }; + _timer.Tick += TimerOnTick; + _timer.Start(); + } + } + private void InitializeComponent() { AvaloniaXamlLoader.Load(this); @@ -36,5 +47,16 @@ namespace IntegrationTestApp ownerRect.Text = $"{owner.Position}, {PixelSize.FromSize(owner.FrameSize!.Value, scaling)}"; } } + + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + _timer?.Stop(); + } + + private void TimerOnTick(object? sender, EventArgs e) + { + _orderTextBox!.Text = MacOSIntegration.GetOrderedIndex(this).ToString(); + } } } From d1b652213a944e1e2b254b475b5d5c6915ca2694 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 1 Sep 2022 22:33:39 +0200 Subject: [PATCH 24/59] Make sure child windows have unique titles/ids. --- samples/IntegrationTestApp/MainWindow.axaml.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 2085b5da2b..f72f83fcb8 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -1,11 +1,13 @@ using System.Collections.Generic; using System.Linq; using Avalonia; +using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.VisualTree; +using Microsoft.CodeAnalysis; namespace IntegrationTestApp { @@ -63,6 +65,17 @@ namespace IntegrationTestApp WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex, }; + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime) + { + // Make sure the windows have unique names and AutomationIds. + var existing = lifetime.Windows.OfType().Count(); + if (existing > 0) + { + AutomationProperties.SetAutomationId(window, window.Name + (existing + 1)); + window.Title += $" {existing + 1}"; + } + } + if (size.HasValue) { window.Width = size.Value.Width; From 1c4a994e2c8796096821c92c8137db0f85e8249f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 1 Sep 2022 22:36:12 +0200 Subject: [PATCH 25/59] Add failing test for #8878. And update the tests to use the value of `NSWindow.orderedIndex` exposed in 0cc0443d34567f0d20082eaee29625a732b93849 instead of relying on appium to give us a z-ordered list of windows. --- .../WindowTests_MacOS.cs | 89 +++++++++---------- 1 file changed, 43 insertions(+), 46 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 4e5344dd25..7eeab2f7f2 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -30,7 +30,7 @@ namespace Avalonia.IntegrationTests.Appium tab.Click(); return; } - catch (WebDriverException e) when (retry++ < 3) + catch (WebDriverException) when (retry++ < 3) { // MacOS sometimes seems to need a bit of time to get itself back in order after switching out // of fullscreen. @@ -49,19 +49,16 @@ namespace Avalonia.IntegrationTests.Appium { mainWindow.Click(); - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - var mainWindowIndex = GetWindowOrder(windows, "MainWindow"); - var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow"); + var secondaryWindowIndex = GetWindowOrder("SecondaryWindow"); - Assert.Equal(0, secondaryWindowIndex); - Assert.Equal(1, mainWindowIndex); + Assert.Equal(1, secondaryWindowIndex); } } [PlatformFact(TestPlatforms.MacOS)] public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip() { - var mainWindow = FindWindow(_session, "MainWindow"); + var mainWindow = GetWindow("MainWindow"); using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.Manual)) { @@ -70,24 +67,21 @@ namespace Avalonia.IntegrationTests.Appium .ClickAndHold() .Perform(); - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - var mainWindowIndex = GetWindowOrder(windows, "MainWindow"); - var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow"); + var secondaryWindowIndex = GetWindowOrder("SecondaryWindow"); new Actions(_session) .MoveToElement(mainWindow, 100, 1) .Release() .Perform(); - Assert.Equal(0, secondaryWindowIndex); - Assert.Equal(1, mainWindowIndex); + Assert.Equal(1, secondaryWindowIndex); } } [PlatformFact(TestPlatforms.MacOS)] public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_In_Fullscreen() { - var mainWindow = FindWindow(_session, "MainWindow"); + var mainWindow = GetWindow("MainWindow"); var buttons = mainWindow.GetChromeButtons(); buttons.maximize.Click(); @@ -98,14 +92,8 @@ namespace Avalonia.IntegrationTests.Appium { using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.Manual)) { - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - var mainWindowIndex = GetWindowOrder(windows, "MainWindow"); - var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow"); - - Assert.Equal(0, secondaryWindowIndex); - Assert.Equal(1, mainWindowIndex); - - Thread.Sleep(5000); + var secondaryWindowIndex = GetWindowOrder("SecondaryWindow"); + Assert.Equal(1, secondaryWindowIndex); } } finally @@ -122,13 +110,8 @@ namespace Avalonia.IntegrationTests.Appium using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Owned, WindowStartupLocation.Manual)) { mainWindow.Click(); - - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - var mainWindowIndex = GetWindowOrder(windows, "MainWindow"); - var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow"); - - Assert.Equal(0, secondaryWindowIndex); - Assert.Equal(1, mainWindowIndex); + var secondaryWindowIndex = GetWindowOrder("SecondaryWindow"); + Assert.Equal(1, secondaryWindowIndex); } } @@ -141,22 +124,35 @@ namespace Avalonia.IntegrationTests.Appium { mainWindow.Click(); - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - var mainWindowIndex = GetWindowOrder(windows, "MainWindow"); - var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow"); + var secondaryWindowIndex = GetWindowOrder("SecondaryWindow"); - Assert.Equal(1, secondaryWindowIndex); - Assert.Equal(0, mainWindowIndex); + Assert.Equal(2, secondaryWindowIndex); var sendToBack = _session.FindElementByAccessibilityId("SendToBack"); sendToBack.Click(); } } + [PlatformFact(TestPlatforms.MacOS)] + public void WindowOrder_Owned_Is_Correct_After_Closing_Window() + { + using (OpenWindow(new PixelSize(300, 500), ShowWindowMode.Owned, WindowStartupLocation.CenterOwner)) + { + // Open a second child window, and close it. + using (OpenWindow(new PixelSize(200, 200), ShowWindowMode.Owned, WindowStartupLocation.CenterOwner)) + { + } + + var secondaryWindowIndex = GetWindowOrder("SecondaryWindow"); + + Assert.Equal(1, secondaryWindowIndex); + } + } + [PlatformFact(TestPlatforms.MacOS)] public void Parent_Window_Has_Disabled_ChromeButtons_When_Modal_Dialog_Shown() { - var window = FindWindow(_session, "MainWindow"); + var window = GetWindow("MainWindow"); var (closeButton, miniaturizeButton, zoomButton) = window.GetChromeButtons(); Assert.True(closeButton.Enabled); @@ -176,7 +172,7 @@ namespace Avalonia.IntegrationTests.Appium { using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) { - var secondaryWindow = FindWindow(_session, "SecondaryWindow"); + var secondaryWindow = GetWindow("SecondaryWindow"); var (closeButton, miniaturizeButton, zoomButton) = secondaryWindow.GetChromeButtons(); Assert.True(closeButton.Enabled); @@ -192,7 +188,7 @@ namespace Avalonia.IntegrationTests.Appium { using (OpenWindow(new PixelSize(200, 100), mode, WindowStartupLocation.Manual)) { - var secondaryWindow = FindWindow(_session, "SecondaryWindow"); + var secondaryWindow = GetWindow("SecondaryWindow"); var (_, miniaturizeButton, _) = secondaryWindow.GetChromeButtons(); miniaturizeButton.Click(); @@ -220,7 +216,7 @@ namespace Avalonia.IntegrationTests.Appium // causes Appium to think it's a different window. OpenWindow(null, ShowWindowMode.Owned, WindowStartupLocation.Manual); - var secondaryWindow = FindWindow(_session, "SecondaryWindow"); + var secondaryWindow = GetWindow("SecondaryWindow"); var hideButton = secondaryWindow.FindElementByAccessibilityId("HideButton"); hideButton.Click(); @@ -236,7 +232,7 @@ namespace Avalonia.IntegrationTests.Appium _session.FindElementByAccessibilityId("RestoreAll").Click(); // Close the window manually. - secondaryWindow = FindWindow(_session, "SecondaryWindow"); + secondaryWindow = GetWindow("SecondaryWindow"); secondaryWindow.GetChromeButtons().close.Click(); } @@ -259,18 +255,19 @@ namespace Avalonia.IntegrationTests.Appium return showButton.OpenWindowWithClick(); } - private static int GetWindowOrder(IReadOnlyCollection elements, string identifier) + private AppiumWebElement GetWindow(string identifier) { - return elements.TakeWhile(x => - x.FindElementByXPath("XCUIElementTypeWindow")?.GetAttribute("identifier") != identifier).Count(); + // The Avalonia a11y tree currently exposes two nested Window elements, this is a bug and should be fixed + // but in the meantime use the `parent::' selector to return the parent "real" window. + return _session.FindElementByXPath( + $"XCUIElementTypeWindow//*[@identifier='{identifier}']/parent::XCUIElementTypeWindow"); } - private static AppiumWebElement FindWindow(AppiumDriver session, string identifier) + private int GetWindowOrder(string identifier) { - var windows = session.FindElementsByXPath("XCUIElementTypeWindow"); - return windows.First(x => - x.FindElementsByXPath("XCUIElementTypeWindow") - .Any(y => y.GetAttribute("identifier") == identifier)); + var window = GetWindow(identifier); + var order = window.FindElementByXPath("//*[@identifier='Order']"); + return int.Parse(order.Text); } public enum ShowWindowMode From b83b795f57f625b8d9e794008ea14c612dbb79f8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 1 Sep 2022 22:38:47 +0200 Subject: [PATCH 26/59] Don't bring old parent window to front. When a window is closed, its parent window is set to null, which caused this code to run. This line caused the parent window to be brought to front, and should have also caused any other child windows to be brought to front, but it seems OSX ignored those requests, causing #8878. Simply don't bring the parent window to the front when a child window is closed. I couldn't work out why this code was necessary anyway, and all integration tests still pass. Fixes #8878. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 2 -- 1 file changed, 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index af8c53cb33..ddc50c26b6 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -91,8 +91,6 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) { if(_parent != nullptr) { _parent->_children.remove(this); - - _parent->BringToFront(); } auto cparent = dynamic_cast(parent); From cc4b8e3ea08037d19ddaf5270eab428da82b28af Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Sep 2022 10:33:06 +0200 Subject: [PATCH 27/59] Fix typo. --- tests/Avalonia.Controls.UnitTests/BorderTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/BorderTests.cs b/tests/Avalonia.Controls.UnitTests/BorderTests.cs index 7af7d1cee2..2cb72f148d 100644 --- a/tests/Avalonia.Controls.UnitTests/BorderTests.cs +++ b/tests/Avalonia.Controls.UnitTests/BorderTests.cs @@ -78,7 +78,7 @@ namespace Avalonia.Controls.UnitTests } }; - var root = CreatedRoot(1.5, target); + var root = CreateRoot(1.5, target); root.LayoutManager.ExecuteInitialLayoutPass(); @@ -101,7 +101,7 @@ namespace Avalonia.Controls.UnitTests } }; - var root = CreatedRoot(1.5, target); + var root = CreateRoot(1.5, target); root.LayoutManager.ExecuteInitialLayoutPass(); @@ -111,7 +111,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Size(104, 104), target.DesiredSize); } - private static TestRoot CreatedRoot( + private static TestRoot CreateRoot( double scaling, Control child, Size? constraint = null) From ac27138120f4b554c61c9e44473713acb0c14a7b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Sep 2022 12:28:56 +0200 Subject: [PATCH 28/59] Add failing tests for #8866. --- .../BorderTests.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/BorderTests.cs b/tests/Avalonia.Controls.UnitTests/BorderTests.cs index 2cb72f148d..e9ddc8e8a3 100644 --- a/tests/Avalonia.Controls.UnitTests/BorderTests.cs +++ b/tests/Avalonia.Controls.UnitTests/BorderTests.cs @@ -111,6 +111,57 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Size(104, 104), target.DesiredSize); } + [Fact] + public void Measure_Arranges_Child_To_Rounded_BorderThickness() + { + Canvas child; + var target = new Border + { + BorderThickness = new Thickness(1), + Width = 82, + Height = 82, + Child = child = new Canvas(), + }; + + var root = CreateRoot(1.5, target); + + root.LayoutManager.ExecuteInitialLayoutPass(); + + // - 1 pixel border thickness is rounded up to 1.3333; for both sides it is 2.6666 + // - Size of 82 needs no rounding + // - Minus border thickness, space for child is 82 - 2.6666 = 79.3333 + Assert.Equal(1.3333, child.Bounds.Left, 3); + Assert.Equal(1.3333, child.Bounds.Top, 3); + Assert.Equal(79.3333, child.Bounds.Width, 3); + Assert.Equal(79.3333, child.Bounds.Height, 3); + } + + [Fact] + public void Measure_Arranges_Child_With_Rounded_Margin() + { + Border child; + var target = new Border + { + Width = 220, + Height = 220, + Child = child = new Border + { + Margin = new Thickness(0, 25, 25, 25), + }, + }; + + var root = CreateRoot(1.5, target); + + root.LayoutManager.ExecuteInitialLayoutPass(); + + // - 25 margin gets rounded up to 25.3333 + // - Size of 220 needs no rounding + Assert.Equal(0, child.Bounds.Left, 3); + Assert.Equal(25.3333, child.Bounds.Top, 3); + Assert.Equal(194.6666, child.Bounds.Width, 3); + Assert.Equal(169.3333, child.Bounds.Height, 3); + } + private static TestRoot CreateRoot( double scaling, Control child, From 76747d32b4cd04ef6cce8718120f28f805a865eb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Sep 2022 12:32:51 +0200 Subject: [PATCH 29/59] Handle FP errors in RoundLayoutValueUp. If the `value` passed to `RoundLayoutValueUp` has a floating point precision error (e.g. 79.333333333333343 in the test case) then when it's multiplied by `dpiScale` and rounded up, it will be rounded up to the a value one greater than it should be. Fix this by rounding the initial value to an arbitrary precision of 8 digits. Fixes #8866 --- src/Avalonia.Base/Layout/LayoutHelper.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Avalonia.Base/Layout/LayoutHelper.cs b/src/Avalonia.Base/Layout/LayoutHelper.cs index 404d19906a..0851dbaea9 100644 --- a/src/Avalonia.Base/Layout/LayoutHelper.cs +++ b/src/Avalonia.Base/Layout/LayoutHelper.cs @@ -251,6 +251,17 @@ namespace Avalonia.Layout { double newValue; + // Round the value to avoid FP errors. This is needed because if `value` has a floating + // point precision error (e.g. 79.333333333333343) then when it's multiplied by + // `dpiScale` and rounded up, it will be rounded up to a value one greater than it + // should be. +#if NET6_0_OR_GREATER + value = Math.Round(value, 8, MidpointRounding.ToZero); +#else + // MidpointRounding.ToZero isn't available in netstandard2.0. + value = Math.Truncate(value * 1e8) / 1e8; +#endif + // If DPI == 1, don't use DPI-aware rounding. if (!MathUtilities.IsOne(dpiScale)) { From 8d9f39d3b3bdbc97c52bb91b30754e159e20fc0c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Sep 2022 13:19:45 +0200 Subject: [PATCH 30/59] Fix copypasta. Fixes #8867. --- src/Avalonia.Controls/Utils/BorderRenderHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs index ecc7cd98bb..eb9f38894d 100644 --- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs +++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs @@ -61,7 +61,7 @@ namespace Avalonia.Controls.Utils _backgroundGeometryCache = null; } - if (boundRect.Width != 0 && innerRect.Height != 0) + if (boundRect.Width != 0 && boundRect.Height != 0) { var borderGeometryKeypoints = new BorderGeometryKeypoints(boundRect, borderThickness, cornerRadius, false); From e05a0d0014160e6b212c54b5ca2a5a5998837876 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Mon, 5 Sep 2022 11:49:33 +0300 Subject: [PATCH 31/59] Fix DashStyle not applying values correctly. --- src/Avalonia.Base/Media/DashStyle.cs | 1 - .../Xaml/ControlTemplateTests.cs | 50 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Media/DashStyle.cs b/src/Avalonia.Base/Media/DashStyle.cs index abee580020..3a30b2d32f 100644 --- a/src/Avalonia.Base/Media/DashStyle.cs +++ b/src/Avalonia.Base/Media/DashStyle.cs @@ -35,7 +35,6 @@ namespace Avalonia.Media /// Initializes a new instance of the class. /// public DashStyle() - : this(null, 0) { } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs index 2d6ed1dc62..203ec5ee3b 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs @@ -1,3 +1,5 @@ +using System; +using System.Linq; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Data; @@ -5,12 +7,49 @@ using Avalonia.Diagnostics; using Avalonia.Markup.Xaml.Templates; using Avalonia.Media; using Avalonia.UnitTests; +using Avalonia.VisualTree; using Xunit; namespace Avalonia.Markup.Xaml.UnitTests.Xaml { public class ControlTemplateTests : XamlTestBase { + [Fact] + public void StyledProperties_Should_Be_Set_In_The_ControlTemplate() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + + var xaml = @" + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var button = (Button)window.Content; + + window.ApplyTemplate(); + button.ApplyTemplate(); + var listBoxHierarchyLine = button.GetVisualChildren().ElementAt(0) as ListBoxHierachyLine; + Assert.Equal(1, listBoxHierarchyLine.LineDashStyle.Offset); + Assert.Equal(2, listBoxHierarchyLine.LineDashStyle.Dashes.Count); + Assert.Equal(2, listBoxHierarchyLine.LineDashStyle.Dashes[0]); + Assert.Equal(2, listBoxHierarchyLine.LineDashStyle.Dashes[1]); + } + + } + [Fact] public void Inline_ControlTemplate_Styled_Values_Are_Set_With_Style_Priority() { @@ -270,4 +309,15 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal("Bar", bar.Name); } } + public class ListBoxHierachyLine : Panel + { + public static readonly StyledProperty LineDashStyleProperty = + AvaloniaProperty.Register(nameof(LineDashStyle)); + + public DashStyle LineDashStyle + { + get => GetValue(LineDashStyleProperty); + set => SetValue(LineDashStyleProperty, value); + } + } } From 99e02059a8c84af73c1d8e46f08e5a4d0c3a2ebb Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Mon, 5 Sep 2022 13:14:38 +0300 Subject: [PATCH 32/59] Fix Test. --- tests/Avalonia.Base.UnitTests/Media/PenTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/Media/PenTests.cs b/tests/Avalonia.Base.UnitTests/Media/PenTests.cs index fecdd30834..6016978fde 100644 --- a/tests/Avalonia.Base.UnitTests/Media/PenTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/PenTests.cs @@ -53,7 +53,10 @@ namespace Avalonia.Base.UnitTests.Media var raised = false; target.Invalidated += (s, e) => raised = true; - dashes.Dashes.Add(0.3); + dashes.Dashes = new AvaloniaList + { + 0.3 + }; Assert.True(raised); } From e64a5e3b0f9d8ebfdc1158b8d3486323e47c3d81 Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Mon, 5 Sep 2022 20:35:13 +0200 Subject: [PATCH 33/59] Fix DBusSystemDialog when path contains spaces --- src/Avalonia.FreeDesktop/DBusSystemDialog.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs index 7974069184..8597f3922a 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs @@ -72,7 +72,7 @@ namespace Avalonia.FreeDesktop using var disposable = await request.WatchResponseAsync(x => tsc.SetResult(x.results["uris"] as string[]), tsc.SetException); var uris = await tsc.Task ?? Array.Empty(); - return uris.Select(path => new BclStorageFile(new FileInfo(new Uri(path).AbsolutePath))).ToList(); + return uris.Select(path => new BclStorageFile(new FileInfo(new Uri(path).LocalPath))).ToList(); } public override async Task SaveFilePickerAsync(FilePickerSaveOptions options) @@ -96,7 +96,7 @@ namespace Avalonia.FreeDesktop var tsc = new TaskCompletionSource(); using var disposable = await request.WatchResponseAsync(x => tsc.SetResult(x.results["uris"] as string[]), tsc.SetException); var uris = await tsc.Task; - var path = uris?.FirstOrDefault() is { } filePath ? new Uri(filePath).AbsolutePath : null; + var path = uris?.FirstOrDefault() is { } filePath ? new Uri(filePath).LocalPath : null; if (path is null) { @@ -126,7 +126,7 @@ namespace Avalonia.FreeDesktop var uris = await tsc.Task ?? Array.Empty(); return uris - .Select(path => new Uri(path).AbsolutePath) + .Select(path => new Uri(path).LocalPath) // WSL2 freedesktop allows to select files as well in directory picker, filter it out. .Where(Directory.Exists) .Select(path => new BclStorageFolder(new DirectoryInfo(path))).ToList(); From 019497c5d44a9a5abe85aff60527b74e71fcf02b Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 7 Sep 2022 12:18:57 +0200 Subject: [PATCH 34/59] fix(DevGenerators): Warning RS1024 Use 'SymbolEqualityComparer' when comparing symbols --- src/tools/DevGenerators/EnumMemberDictionaryGenerator.cs | 4 ++-- src/tools/DevGenerators/GetProcAddressInitialization.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/DevGenerators/EnumMemberDictionaryGenerator.cs b/src/tools/DevGenerators/EnumMemberDictionaryGenerator.cs index 42612c7ce0..86dbb3a452 100644 --- a/src/tools/DevGenerators/EnumMemberDictionaryGenerator.cs +++ b/src/tools/DevGenerators/EnumMemberDictionaryGenerator.cs @@ -32,7 +32,7 @@ public class EnumMemberDictionaryGenerator : IIncrementalGenerator ).Collect(); context.RegisterSourceOutput(all, static (context, methods) => { - foreach (var typeGroup in methods.GroupBy(f => f.ContainingType)) + foreach (var typeGroup in methods.GroupBy(f => f.ContainingType, SymbolEqualityComparer.Default)) { var classBuilder = new StringBuilder(); if (typeGroup.Key.ContainingNamespace != null) @@ -91,4 +91,4 @@ public class EnumMemberDictionaryGenerator : IIncrementalGenerator } -} \ No newline at end of file +} diff --git a/src/tools/DevGenerators/GetProcAddressInitialization.cs b/src/tools/DevGenerators/GetProcAddressInitialization.cs index 64698d1f2e..c7d03a9b50 100644 --- a/src/tools/DevGenerators/GetProcAddressInitialization.cs +++ b/src/tools/DevGenerators/GetProcAddressInitialization.cs @@ -34,7 +34,7 @@ public class GetProcAddressInitializationGenerator : IIncrementalGenerator var all = fieldsWithAttribute.Collect(); context.RegisterSourceOutput(all, static (context, methods) => { - foreach (var typeGroup in methods.GroupBy(f => f.ContainingType)) + foreach (var typeGroup in methods.GroupBy(f => f.ContainingType, SymbolEqualityComparer.Default)) { var nextContext = 0; var contexts = new Dictionary(); @@ -335,4 +335,4 @@ public class GetProcAddressInitializationGenerator : IIncrementalGenerator return name.ToString(); } -} \ No newline at end of file +} From f69ae35158819d7040e9dec6da9f2f840840015f Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 7 Sep 2022 16:46:22 +0300 Subject: [PATCH 35/59] Fix unpredictable choice between methods when using method binding. --- .../Data/Core/Plugins/MethodAccessorPlugin.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs index 1ca70140ec..0d51a6ed36 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs @@ -55,13 +55,20 @@ namespace Avalonia.Data.Core.Plugins var methods = type.GetMethods(bindingFlags); - foreach (MethodInfo methodInfo in methods) + foreach (var methodInfo in methods) { if (methodInfo.Name == methodName) { - found = methodInfo; - - break; + var parameters = methodInfo.GetParameters(); + if (parameters.Length == 1 && parameters[0].ParameterType == typeof(object)) + { + found = methodInfo; + break; + } + else if (parameters.Length == 0) + { + found = methodInfo; + } } } From 9545bbd6b08f38abdf2c9a877b4e0a2145b990d9 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 7 Sep 2022 15:22:51 +0200 Subject: [PATCH 36/59] fix(DevGenerators): Some null annotation --- .../CompositionRoslynGenerator.cs | 7 +++-- .../CompositionGenerator/Extensions.cs | 30 +++++++++---------- .../CompositionGenerator/Generator.Utils.cs | 4 +-- .../CompositionGenerator/Generator.cs | 16 +++++----- src/tools/DevGenerators/DevGenerators.csproj | 1 + .../GetProcAddressInitialization.cs | 11 ++++--- 6 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/tools/DevGenerators/CompositionGenerator/CompositionRoslynGenerator.cs b/src/tools/DevGenerators/CompositionGenerator/CompositionRoslynGenerator.cs index 72311b4d18..26bd4796df 100644 --- a/src/tools/DevGenerators/CompositionGenerator/CompositionRoslynGenerator.cs +++ b/src/tools/DevGenerators/CompositionGenerator/CompositionRoslynGenerator.cs @@ -11,8 +11,9 @@ namespace Avalonia.SourceGenerator.CompositionGenerator { var schema = context.AdditionalTextsProvider.Where(static file => file.Path.EndsWith("composition-schema.xml")); - var configs = schema.Select((t, _) => - (GConfig)new XmlSerializer(typeof(GConfig)).Deserialize(new StringReader(t.GetText().ToString()))); + var configs = schema.Select((t, _) => t.GetText()) + .Where(source => source is not null) + .Select((source, _) => (GConfig)new XmlSerializer(typeof(GConfig)).Deserialize(new StringReader(source!.ToString()))); context.RegisterSourceOutput(configs, (spc, config) => { var generator = new Generator(new RoslynCompositionGeneratorSink(spc), config); @@ -20,4 +21,4 @@ namespace Avalonia.SourceGenerator.CompositionGenerator }); } } -} \ No newline at end of file +} diff --git a/src/tools/DevGenerators/CompositionGenerator/Extensions.cs b/src/tools/DevGenerators/CompositionGenerator/Extensions.cs index d88e9b4600..f34765ec36 100644 --- a/src/tools/DevGenerators/CompositionGenerator/Extensions.cs +++ b/src/tools/DevGenerators/CompositionGenerator/Extensions.cs @@ -7,49 +7,47 @@ namespace Avalonia.SourceGenerator.CompositionGenerator { public static class Extensions { - public static ClassDeclarationSyntax AddModifiers(this ClassDeclarationSyntax cl, params SyntaxKind[] modifiers) + public static ClassDeclarationSyntax AddModifiers(this ClassDeclarationSyntax cl, params SyntaxKind[]? modifiers) { - if (modifiers == null) + if (modifiers is null) return cl; return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); } - public static MethodDeclarationSyntax AddModifiers(this MethodDeclarationSyntax cl, params SyntaxKind[] modifiers) + public static MethodDeclarationSyntax AddModifiers(this MethodDeclarationSyntax cl, params SyntaxKind[]? modifiers) { - if (modifiers == null) + if (modifiers is null) return cl; return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); } - public static PropertyDeclarationSyntax AddModifiers(this PropertyDeclarationSyntax cl, params SyntaxKind[] modifiers) + public static PropertyDeclarationSyntax AddModifiers(this PropertyDeclarationSyntax cl, params SyntaxKind[]? modifiers) { - if (modifiers == null) + if (modifiers is null) return cl; return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); } - public static ConstructorDeclarationSyntax AddModifiers(this ConstructorDeclarationSyntax cl, params SyntaxKind[] modifiers) + public static ConstructorDeclarationSyntax AddModifiers(this ConstructorDeclarationSyntax cl, params SyntaxKind[]? modifiers) { - if (modifiers == null) + if (modifiers is null) return cl; return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); } - public static AccessorDeclarationSyntax AddModifiers(this AccessorDeclarationSyntax cl, params SyntaxKind[] modifiers) + public static AccessorDeclarationSyntax AddModifiers(this AccessorDeclarationSyntax cl, params SyntaxKind[]? modifiers) { - if (modifiers == null) + if (modifiers is null) return cl; return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); } - public static EnumDeclarationSyntax AddModifiers(this EnumDeclarationSyntax cl, params SyntaxKind[] modifiers) + public static EnumDeclarationSyntax AddModifiers(this EnumDeclarationSyntax cl, params SyntaxKind[]? modifiers) { - if (modifiers == null) + if (modifiers is null) return cl; return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); } - - public static string WithLowerFirst(this string s) { @@ -58,7 +56,7 @@ namespace Avalonia.SourceGenerator.CompositionGenerator return char.ToLowerInvariant(s[0]) + s.Substring(1); } - public static ExpressionSyntax MemberAccess(params string[] identifiers) + public static ExpressionSyntax MemberAccess(params string[]? identifiers) { if (identifiers == null || identifiers.Length == 0) throw new ArgumentException(); @@ -96,4 +94,4 @@ namespace Avalonia.SourceGenerator.CompositionGenerator ? s.Substring(prefix.Length) : s; } -} \ No newline at end of file +} diff --git a/src/tools/DevGenerators/CompositionGenerator/Generator.Utils.cs b/src/tools/DevGenerators/CompositionGenerator/Generator.Utils.cs index b53c247991..16aa15d95a 100644 --- a/src/tools/DevGenerators/CompositionGenerator/Generator.Utils.cs +++ b/src/tools/DevGenerators/CompositionGenerator/Generator.Utils.cs @@ -54,7 +54,7 @@ namespace Avalonia.SourceGenerator.CompositionGenerator FieldDeclarationSyntax DeclareField(string type, string name, params SyntaxKind[] modifiers) => DeclareField(type, name, null, modifiers); - FieldDeclarationSyntax DeclareField(string type, string name, EqualsValueClauseSyntax initializer, + FieldDeclarationSyntax DeclareField(string type, string name, EqualsValueClauseSyntax? initializer, params SyntaxKind[] modifiers) => FieldDeclaration( VariableDeclaration(ParseTypeName(type), @@ -63,4 +63,4 @@ namespace Avalonia.SourceGenerator.CompositionGenerator .WithSemicolonToken(Semicolon()) .WithModifiers(TokenList(modifiers.Select(x => Token(x)))); } -} \ No newline at end of file +} diff --git a/src/tools/DevGenerators/CompositionGenerator/Generator.cs b/src/tools/DevGenerators/CompositionGenerator/Generator.cs index 18f1d1c1e5..cbcc55ada0 100644 --- a/src/tools/DevGenerators/CompositionGenerator/Generator.cs +++ b/src/tools/DevGenerators/CompositionGenerator/Generator.cs @@ -37,8 +37,8 @@ namespace Avalonia.SourceGenerator.CompositionGenerator - string ServerName(string c) => c != null ? ("Server" + c) : "ServerObject"; - string ChangesName(string c) => c != null ? (c + "Changes") : "ChangeSet"; + string ServerName(string? c) => c != null ? ("Server" + c) : "ServerObject"; + string ChangesName(string? c) => c != null ? (c + "Changes") : "ChangeSet"; string ChangedFieldsTypeName(GClass c) => c.Name + "ChangedFields"; string ChangedFieldsFieldName(GClass c) => "_changedFieldsOf" + c.Name; string PropertyBackingFieldName(GProperty prop) => "_" + prop.Name.WithLowerFirst(); @@ -298,10 +298,10 @@ namespace Avalonia.SourceGenerator.CompositionGenerator if(cl.Implements.Count > 0) foreach (var impl in cl.Implements) { - client = client.WithBaseList(client.BaseList.AddTypes(SimpleBaseType(ParseTypeName(impl.Name)))); + client = client.WithBaseList(client.BaseList?.AddTypes(SimpleBaseType(ParseTypeName(impl.Name)))); if (impl.ServerName != null) server = server.WithBaseList( - server.BaseList.AddTypes(SimpleBaseType(ParseTypeName(impl.ServerName)))); + server.BaseList?.AddTypes(SimpleBaseType(ParseTypeName(impl.ServerName)))); client = client.AddMembers( ParseMemberDeclaration($"{impl.ServerName} {impl.Name}.Server => Server;")); @@ -527,7 +527,7 @@ var changed = reader.Read<{ChangedFieldsTypeName(cl)}>(); body = body.AddStatements( ParseStatement("return base.GetPropertyForAnimation(name);")); var method = ((MethodDeclarationSyntax) ParseMemberDeclaration( - $"public override Avalonia.Rendering.Composition.Expressions.ExpressionVariant GetPropertyForAnimation(string name){{}}")) + $"public override Avalonia.Rendering.Composition.Expressions.ExpressionVariant GetPropertyForAnimation(string name){{}}")!) .WithBody(body); return cl.AddMembers(method); @@ -540,7 +540,7 @@ var changed = reader.Read<{ChangedFieldsTypeName(cl)}>(); body = body.AddStatements( ParseStatement("return base.GetCompositionProperty(name);")); var method = ((MethodDeclarationSyntax)ParseMemberDeclaration( - $"public override CompositionProperty? GetCompositionProperty(string name){{}}")) + $"public override CompositionProperty? GetCompositionProperty(string name){{}}")!) .WithBody(body); return cl.AddMembers(method); @@ -559,11 +559,11 @@ var changed = reader.Read<{ChangedFieldsTypeName(cl)}>(); ); return cl.AddMembers( ((MethodDeclarationSyntax) ParseMemberDeclaration( - "internal override void StartAnimation(string propertyName, CompositionAnimation animation, Avalonia.Rendering.Composition.Expressions.ExpressionVariant? finalValue){}")) + "internal override void StartAnimation(string propertyName, CompositionAnimation animation, Avalonia.Rendering.Composition.Expressions.ExpressionVariant? finalValue){}")!) .WithBody(body)); } } -} \ No newline at end of file +} diff --git a/src/tools/DevGenerators/DevGenerators.csproj b/src/tools/DevGenerators/DevGenerators.csproj index 30da940514..069ff159fc 100644 --- a/src/tools/DevGenerators/DevGenerators.csproj +++ b/src/tools/DevGenerators/DevGenerators.csproj @@ -4,6 +4,7 @@ netstandard2.0 enable false + 10 diff --git a/src/tools/DevGenerators/GetProcAddressInitialization.cs b/src/tools/DevGenerators/GetProcAddressInitialization.cs index c7d03a9b50..aedc13e7f6 100644 --- a/src/tools/DevGenerators/GetProcAddressInitialization.cs +++ b/src/tools/DevGenerators/GetProcAddressInitialization.cs @@ -131,10 +131,13 @@ public class GetProcAddressInitializationGenerator : IIncrementalGenerator .Append(".GetProcAddress(") .Append("getProcAddress, ") .Append(contextName); - - var syntaxNode = (AttributeSyntax)attr.ApplicationSyntaxReference.GetSyntax(); - foreach (var arg in syntaxNode.ArgumentList.Arguments) - initializeBody.Append(", ").Append(arg.GetText()); + + if (attr.ApplicationSyntaxReference?.GetSyntax() is AttributeSyntax syntaxNode + && syntaxNode.ArgumentList is { }) + { + foreach (var arg in syntaxNode.ArgumentList.Arguments) + initializeBody.Append(", ").Append(arg.GetText()); + } initializeBody.AppendLine(");"); } } From 5501010f2dab979366a3a24a22cfd514d399d360 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 7 Sep 2022 15:17:04 +0100 Subject: [PATCH 37/59] remove wwwroot --- .../wwwroot/Avalonia/Avalonia.js | 6 - .../wwwroot/Avalonia/DpiWatcher.js | 26 --- .../wwwroot/Avalonia/InputHelper.js | 19 -- .../wwwroot/Avalonia/NativeControlHost.js | 48 ----- .../wwwroot/Avalonia/SKHtmlCanvas.js | 172 --------------- .../wwwroot/Avalonia/SizeWatcher.js | 39 ---- .../wwwroot/Storage/IndexedDbWrapper.js | 72 ------- .../wwwroot/Storage/StorageProvider.js | 199 ------------------ 8 files changed, 581 deletions(-) delete mode 100644 src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/Avalonia.js delete mode 100644 src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/DpiWatcher.js delete mode 100644 src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/InputHelper.js delete mode 100644 src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/NativeControlHost.js delete mode 100644 src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SKHtmlCanvas.js delete mode 100644 src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SizeWatcher.js delete mode 100644 src/Web/Avalonia.Web.Blazor/wwwroot/Storage/IndexedDbWrapper.js delete mode 100644 src/Web/Avalonia.Web.Blazor/wwwroot/Storage/StorageProvider.js diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/Avalonia.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/Avalonia.js deleted file mode 100644 index 849ea089d5..0000000000 --- a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/Avalonia.js +++ /dev/null @@ -1,6 +0,0 @@ -export { DpiWatcher } from "./DpiWatcher"; -export { InputHelper } from "./InputHelper"; -export { NativeControlHost } from "./NativeControlHost"; -export { SizeWatcher } from "./SizeWatcher"; -export { SKHtmlCanvas } from "./SKHtmlCanvas"; -//# sourceMappingURL=Avalonia.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/DpiWatcher.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/DpiWatcher.js deleted file mode 100644 index 17b6999108..0000000000 --- a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/DpiWatcher.js +++ /dev/null @@ -1,26 +0,0 @@ -export class DpiWatcher { - static getDpi() { - return window.devicePixelRatio; - } - static start(callback) { - DpiWatcher.lastDpi = window.devicePixelRatio; - DpiWatcher.timerId = window.setInterval(DpiWatcher.update, 1000); - DpiWatcher.callback = callback; - return DpiWatcher.lastDpi; - } - static stop() { - 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); - } - } -} -//# sourceMappingURL=DpiWatcher.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/InputHelper.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/InputHelper.js deleted file mode 100644 index 97a4500a00..0000000000 --- a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/InputHelper.js +++ /dev/null @@ -1,19 +0,0 @@ -export class InputHelper { - static clear(inputElement) { - inputElement.value = ""; - } - static focus(inputElement) { - inputElement.focus(); - inputElement.setSelectionRange(0, 0); - } - static setCursor(inputElement, kind) { - inputElement.style.cursor = kind; - } - static hide(inputElement) { - inputElement.style.display = 'none'; - } - static show(inputElement) { - inputElement.style.display = 'block'; - } -} -//# sourceMappingURL=InputHelper.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/NativeControlHost.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/NativeControlHost.js deleted file mode 100644 index 05f804ed8c..0000000000 --- a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/NativeControlHost.js +++ /dev/null @@ -1,48 +0,0 @@ -export class NativeControlHost { - static CreateDefaultChild(parent) { - return document.createElement("div"); - } - static GetReference(element) { - return element; - } - static CreateAttachment() { - return new NativeControlHostTopLevelAttachment(); - } -} -class NativeControlHostTopLevelAttachment { - InitializeWithChildHandle(child) { - this._child = child; - this._child.style.position = "absolute"; - } - AttachTo(host) { - 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, y, width, height) { - 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, height) { - if (this._child) { - this._child.style.width = width + "px"; - this._child.style.height = height + "px"; - this._child.style.display = "none"; - } - } - ReleaseChild() { - if (this._child) { - this._child = undefined; - } - } -} -//# sourceMappingURL=NativeControlHost.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SKHtmlCanvas.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SKHtmlCanvas.js deleted file mode 100644 index 5900026acd..0000000000 --- a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SKHtmlCanvas.js +++ /dev/null @@ -1,172 +0,0 @@ -export class SKHtmlCanvas { - constructor(useGL, element, callback) { - this.renderLoopEnabled = false; - this.renderLoopRequest = 0; - 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; - } - GL.makeContextCurrent(ctx); - const fbo = GLctx.getParameter(GLctx.FRAMEBUFFER_BINDING); - this.glInfo = { - context: ctx, - fboId: fbo ? fbo.id : 0, - stencil: GLctx.getParameter(GLctx.STENCIL_BITS), - sample: 0, - depth: GLctx.getParameter(GLctx.DEPTH_BITS), - }; - } - } - static initGL(element, elementId, callback) { - var view = SKHtmlCanvas.init(true, element, elementId, callback); - if (!view || !view.glInfo) - return null; - return view.glInfo; - } - static initRaster(element, elementId, callback) { - var view = SKHtmlCanvas.init(false, element, elementId, callback); - if (!view) - return false; - return true; - } - static init(useGL, element, elementId, callback) { - var htmlCanvas = element; - 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; - } - static deinit(elementId) { - if (!elementId) - return; - const element = SKHtmlCanvas.elements.get(elementId); - SKHtmlCanvas.elements.delete(elementId); - const htmlCanvas = element; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - htmlCanvas.SKHtmlCanvas.deinit(); - htmlCanvas.SKHtmlCanvas = undefined; - } - static requestAnimationFrame(element, renderLoop) { - const htmlCanvas = element; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - htmlCanvas.SKHtmlCanvas.requestAnimationFrame(renderLoop); - } - static setCanvasSize(element, width, height) { - const htmlCanvas = element; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - htmlCanvas.SKHtmlCanvas.setCanvasSize(width, height); - } - static setEnableRenderLoop(element, enable) { - const htmlCanvas = element; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - htmlCanvas.SKHtmlCanvas.setEnableRenderLoop(enable); - } - static putImageData(element, pData, width, height) { - const htmlCanvas = element; - if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) - return; - htmlCanvas.SKHtmlCanvas.putImageData(pData, width, height); - } - deinit() { - this.setEnableRenderLoop(false); - } - setCanvasSize(width, height) { - 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) { - GL.makeContextCurrent(this.glInfo.context); - } - } - requestAnimationFrame(renderLoop) { - if (renderLoop !== undefined && this.renderLoopEnabled !== renderLoop) - this.setEnableRenderLoop(renderLoop); - if (this.renderLoopRequest !== 0) - return; - this.renderLoopRequest = window.requestAnimationFrame(() => { - if (this.glInfo) { - 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; - if (this.renderLoopEnabled) - this.requestAnimationFrame(); - }); - } - setEnableRenderLoop(enable) { - this.renderLoopEnabled = enable; - if (enable) { - this.requestAnimationFrame(); - } - else if (this.renderLoopRequest !== 0) { - window.cancelAnimationFrame(this.renderLoopRequest); - this.renderLoopRequest = 0; - } - } - putImageData(pData, width, height) { - 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; - } - this.htmlCanvas.width = width; - this.htmlCanvas.height = height; - 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) { - 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 = 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; - } -} -//# sourceMappingURL=SKHtmlCanvas.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SizeWatcher.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SizeWatcher.js deleted file mode 100644 index 1c6ce26155..0000000000 --- a/src/Web/Avalonia.Web.Blazor/wwwroot/Avalonia/SizeWatcher.js +++ /dev/null @@ -1,39 +0,0 @@ -export class SizeWatcher { - static observe(element, elementId, callback) { - if (!element || !callback) - return; - SizeWatcher.init(); - const watcherElement = element; - watcherElement.SizeWatcher = { - callback: callback - }; - SizeWatcher.elements.set(elementId, element); - SizeWatcher.observer.observe(element); - SizeWatcher.invoke(element); - } - static unobserve(elementId) { - if (!elementId || !SizeWatcher.observer) - return; - const element = SizeWatcher.elements.get(elementId); - SizeWatcher.elements.delete(elementId); - SizeWatcher.observer.unobserve(element); - } - static init() { - if (SizeWatcher.observer) - return; - SizeWatcher.elements = new Map(); - SizeWatcher.observer = new ResizeObserver((entries) => { - for (let entry of entries) { - SizeWatcher.invoke(entry.target); - } - }); - } - static invoke(element) { - const watcherElement = element; - const instance = watcherElement.SizeWatcher; - if (!instance || !instance.callback) - return; - return instance.callback.invokeMethod('Invoke', element.clientWidth, element.clientHeight); - } -} -//# sourceMappingURL=SizeWatcher.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Storage/IndexedDbWrapper.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Storage/IndexedDbWrapper.js deleted file mode 100644 index bf9cab01bc..0000000000 --- a/src/Web/Avalonia.Web.Blazor/wwwroot/Storage/IndexedDbWrapper.js +++ /dev/null @@ -1,72 +0,0 @@ -class InnerDbConnection { - constructor(database) { - this.database = database; - } - openStore(store, mode) { - const tx = this.database.transaction(store, mode); - return tx.objectStore(store); - } - put(store, obj, key) { - 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); - }; - }); - } - get(store, key) { - 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); - }; - }); - } - delete(store, key) { - const os = this.openStore(store, "readwrite"); - return new Promise((resolve, reject) => { - const response = os.delete(key); - response.onsuccess = () => { - resolve(); - }; - response.onerror = () => { - reject(response.error); - }; - }); - } - close() { - this.database.close(); - } -} -export class IndexedDbWrapper { - constructor(databaseName, objectStores) { - this.databaseName = databaseName; - this.objectStores = objectStores; - } - connect() { - 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); - }; - }); - } -} -//# sourceMappingURL=IndexedDbWrapper.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Web.Blazor/wwwroot/Storage/StorageProvider.js b/src/Web/Avalonia.Web.Blazor/wwwroot/Storage/StorageProvider.js deleted file mode 100644 index 042a83d177..0000000000 --- a/src/Web/Avalonia.Web.Blazor/wwwroot/Storage/StorageProvider.js +++ /dev/null @@ -1,199 +0,0 @@ -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __asyncValues = (this && this.__asyncValues) || function (o) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var m = o[Symbol.asyncIterator], i; - return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); - function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } - function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } -}; -import { IndexedDbWrapper } from "./IndexedDbWrapper"; -const fileBookmarksStore = "fileBookmarks"; -const avaloniaDb = new IndexedDbWrapper("AvaloniaDb", [ - fileBookmarksStore -]); -class StorageItem { - constructor(handle, bookmarkId) { - this.handle = handle; - this.bookmarkId = bookmarkId; - } - getName() { - return this.handle.name; - } - getKind() { - return this.handle.kind; - } - openRead() { - return __awaiter(this, void 0, void 0, function* () { - if (!(this.handle instanceof FileSystemFileHandle)) { - throw new Error("StorageItem is not a file"); - } - yield this.verityPermissions('read'); - const file = yield this.handle.getFile(); - return file; - }); - } - openWrite() { - return __awaiter(this, void 0, void 0, function* () { - if (!(this.handle instanceof FileSystemFileHandle)) { - throw new Error("StorageItem is not a file"); - } - yield this.verityPermissions('readwrite'); - return yield this.handle.createWritable({ keepExistingData: true }); - }); - } - getProperties() { - return __awaiter(this, void 0, void 0, function* () { - const file = this.handle instanceof FileSystemFileHandle - && (yield this.handle.getFile()); - if (!file) { - return null; - } - return { - Size: file.size, - LastModified: file.lastModified, - Type: file.type - }; - }); - } - getItems() { - var e_1, _a; - return __awaiter(this, void 0, void 0, function* () { - if (this.handle.kind !== "directory") { - return new StorageItems([]); - } - const items = []; - try { - for (var _b = __asyncValues(this.handle.entries()), _c; _c = yield _b.next(), !_c.done;) { - const [key, value] = _c.value; - items.push(new StorageItem(value)); - } - } - catch (e_1_1) { e_1 = { error: e_1_1 }; } - finally { - try { - if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b); - } - finally { if (e_1) throw e_1.error; } - } - return new StorageItems(items); - }); - } - verityPermissions(mode) { - return __awaiter(this, void 0, void 0, function* () { - if ((yield this.handle.queryPermission({ mode })) === 'granted') { - return; - } - if ((yield this.handle.requestPermission({ mode })) === "denied") { - throw new Error("Read permissions denied"); - } - }); - } - saveBookmark() { - return __awaiter(this, void 0, void 0, function* () { - if (this.bookmarkId) { - return this.bookmarkId; - } - const connection = yield avaloniaDb.connect(); - try { - const key = yield connection.put(fileBookmarksStore, this.handle, this.generateBookmarkId()); - return key; - } - finally { - connection.close(); - } - }); - } - deleteBookmark() { - return __awaiter(this, void 0, void 0, function* () { - if (!this.bookmarkId) { - return; - } - const connection = yield avaloniaDb.connect(); - try { - const key = yield connection.delete(fileBookmarksStore, this.bookmarkId); - } - finally { - connection.close(); - } - }); - } - generateBookmarkId() { - return Date.now().toString(36) + Math.random().toString(36).substring(2); - } -} -class StorageItems { - constructor(items) { - this.items = items; - } - count() { - return this.items.length; - } - at(index) { - return this.items[index]; - } -} -export class StorageProvider { - static canOpen() { - return typeof window.showOpenFilePicker !== 'undefined'; - } - static canSave() { - return typeof window.showSaveFilePicker !== 'undefined'; - } - static canPickFolder() { - return typeof window.showDirectoryPicker !== 'undefined'; - } - static selectFolderDialog(startIn) { - return __awaiter(this, void 0, void 0, function* () { - const options = { - startIn: ((startIn === null || startIn === void 0 ? void 0 : startIn.handle) || undefined) - }; - const handle = yield window.showDirectoryPicker(options); - return new StorageItem(handle); - }); - } - static openFileDialog(startIn, multiple, types, excludeAcceptAllOption) { - return __awaiter(this, void 0, void 0, function* () { - const options = { - startIn: ((startIn === null || startIn === void 0 ? void 0 : startIn.handle) || undefined), - multiple, - excludeAcceptAllOption, - types: (types || undefined) - }; - const handles = yield window.showOpenFilePicker(options); - return new StorageItems(handles.map((handle) => new StorageItem(handle))); - }); - } - static saveFileDialog(startIn, suggestedName, types, excludeAcceptAllOption) { - return __awaiter(this, void 0, void 0, function* () { - const options = { - startIn: ((startIn === null || startIn === void 0 ? void 0 : startIn.handle) || undefined), - suggestedName: (suggestedName || undefined), - excludeAcceptAllOption, - types: (types || undefined) - }; - const handle = yield window.showSaveFilePicker(options); - return new StorageItem(handle); - }); - } - static openBookmark(key) { - return __awaiter(this, void 0, void 0, function* () { - const connection = yield avaloniaDb.connect(); - try { - const handle = yield connection.get(fileBookmarksStore, key); - return handle && new StorageItem(handle, key); - } - finally { - connection.close(); - } - }); - } -} -//# sourceMappingURL=StorageProvider.js.map \ No newline at end of file From baf29e273d3d72663453f5323221d49e71117cfe Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 7 Sep 2022 15:27:28 +0100 Subject: [PATCH 38/59] ignore wwwroot --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0f0e153e9c..84faae1806 100644 --- a/.gitignore +++ b/.gitignore @@ -214,3 +214,4 @@ src/Web/Avalonia.Web.Blazor/wwwroot/*.js src/Web/Avalonia.Web.Blazor/Interop/Typescript/*.js node_modules src/Web/Avalonia.Web.Blazor/webapp/package-lock.json +src/Web/Avalonia.Web.Blazor/wwwroot From 33de9380a5e7399494db7fe64af75d8d755b7b72 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 7 Sep 2022 18:54:10 +0200 Subject: [PATCH 39/59] feat(DevAnalyzer): OnPropertyChanged override Analyzer --- .../OnPropertyChangedOverrideAnalyzer.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/tools/DevAnalyzers/OnPropertyChangedOverrideAnalyzer.cs diff --git a/src/tools/DevAnalyzers/OnPropertyChangedOverrideAnalyzer.cs b/src/tools/DevAnalyzers/OnPropertyChangedOverrideAnalyzer.cs new file mode 100644 index 0000000000..a16542fdc0 --- /dev/null +++ b/src/tools/DevAnalyzers/OnPropertyChangedOverrideAnalyzer.cs @@ -0,0 +1,64 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace DevAnalyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class OnPropertyChangedOverrideAnalyzer : DiagnosticAnalyzer + { + public const string DiagnosticId = "AVADEV2001"; + + private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( + DiagnosticId, + "Missing invoke base.OnPropertyChanged", + "Method '{0}' do not invoke base.{0}", + "Potential issue", + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "The OnPropertyChanged of the base class was not invoked in the override method declaration, which could lead to unwanted behavior."); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method); + } + + private static void AnalyzeMethod(SymbolAnalysisContext context) + { + if (context.Symbol is IMethodSymbol currentMethod + && currentMethod.Name == "OnPropertyChanged" + && currentMethod.OverriddenMethod is IMethodSymbol originalMethod) + { + var declaration = currentMethod.DeclaringSyntaxReferences.FirstOrDefault() + ?.GetSyntax(context.CancellationToken); + if (declaration is not null && context.Compilation.GetSemanticModel(declaration!.SyntaxTree) is { } semanticModel) + { + if (declaration.SyntaxTree.TryGetRoot(out var root)) + { + var baseInvocations = root.DescendantNodes().OfType(); + if (baseInvocations.Any()) + { + foreach (var baseInvocation in baseInvocations) + { + var parent = baseInvocation.Parent; + var targetSymbol = semanticModel.GetSymbolInfo(parent, context.CancellationToken); + if (SymbolEqualityComparer.Default.Equals(targetSymbol.Symbol, originalMethod)) + { + return; + } + } + } + context.ReportDiagnostic(Diagnostic.Create(Rule, currentMethod.Locations[0], currentMethod.Name)); + } + } + } + } + + } +} From f45617b23cfae909c0a533e2edf283f770b00445 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 26 Aug 2022 15:10:10 +0100 Subject: [PATCH 40/59] use composition on wasm. --- src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs | 5 +++-- src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index 09128d714d..1bba8327bf 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs @@ -6,6 +6,7 @@ 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; @@ -352,9 +353,9 @@ namespace Avalonia.Web.Blazor // We also don't want to have it as a meaningful public API. // Therefore we have InternalsVisibleTo hack here. - if (_topLevel.Renderer is DeferredRenderer dr) + if (_topLevel.Renderer is CompositingRenderer dr) { - dr.Render(true); + dr.CompositionTarget.ImmediateUIThreadRender(); } } diff --git a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs index af9a07927e..86860ebd58 100644 --- a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs +++ b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs @@ -7,6 +7,7 @@ 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; @@ -146,7 +147,7 @@ namespace Avalonia.Web.Blazor public IRenderer CreateRenderer(IRenderRoot root) { var loop = AvaloniaLocator.Current.GetRequiredService(); - return new DeferredRenderer(root, loop); + return new CompositingRenderer(root, new Compositor(loop, null)); } public void Invalidate(Rect rect) From 0f9273c3b53baeb7ff7f9f5f9c72489678976e49 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 7 Sep 2022 21:35:54 +0100 Subject: [PATCH 41/59] fix tab key handling, by making sure the canvas is re-focused when the ime is closed. --- src/Web/Avalonia.Web.Blazor/AvaloniaView.razor | 8 +++++++- src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs | 13 +++++++++++-- .../Interop/InputHelperInterop.cs | 4 ++-- .../webapp/modules/Avalonia/InputHelper.ts | 13 +++++++++++-- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor index 4802191077..27a6acd0de 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor @@ -1,7 +1,11 @@ -
diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index 1bba8327bf..f3a45aac56 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs @@ -32,10 +32,12 @@ namespace Avalonia.Web.Blazor private AvaloniaModule? _avaloniaModule = null; private InputHelperInterop? _inputHelper = null; private InputHelperInterop? _canvasHelper = null; + private InputHelperInterop? _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); @@ -247,8 +249,9 @@ namespace Avalonia.Web.Blazor _inputHelper = new InputHelperInterop(_avaloniaModule, _inputElement); _canvasHelper = new InputHelperInterop(_avaloniaModule, _htmlCanvas); + _containerHelper = new InputHelperInterop(_avaloniaModule, _containerElement); - _inputHelper.Hide(); + HideIme(); _canvasHelper.SetCursor("default"); _topLevelImpl.SetCssCursor = x => { @@ -387,6 +390,12 @@ namespace Avalonia.Web.Blazor } } + private void HideIme() + { + _inputHelper?.Hide(); + _containerHelper?.Focus(); + } + public void SetClient(ITextInputMethodClient? client) { if (_inputHelper is null) @@ -407,7 +416,7 @@ namespace Avalonia.Web.Blazor else { _inputElementFocused = false; - _inputHelper.Hide(); + HideIme(); } } diff --git a/src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs index 294e71eb1f..ebc80618b1 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs +++ b/src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs @@ -13,10 +13,10 @@ namespace Avalonia.Web.Blazor.Interop private readonly AvaloniaModule _module; private readonly ElementReference _inputElement; - public InputHelperInterop(AvaloniaModule module, ElementReference element) + public InputHelperInterop(AvaloniaModule module, ElementReference inputElement) { _module = module; - _inputElement = element; + _inputElement = inputElement; } public void Clear() => _module.Invoke(ClearSymbol, _inputElement); diff --git a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts index 2cce411376..8619fc8cee 100644 --- a/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts +++ b/src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts @@ -3,9 +3,18 @@ inputElement.value = ""; } - public static focus(inputElement: HTMLInputElement) { + + public static isInputElement( element : HTMLInputElement | HTMLElement ) : element is HTMLInputElement { + return ( element as HTMLInputElement).setSelectionRange !== undefined; + } + + public static focus(inputElement: HTMLElement) { inputElement.focus(); - inputElement.setSelectionRange(0, 0); + + if(this.isInputElement(inputElement)) + { + (inputElement as HTMLInputElement).setSelectionRange(0,0); + } } public static setCursor(inputElement: HTMLInputElement, kind: string) { From b9f7270eb978258c60d95823cb5b263d17a32c38 Mon Sep 17 00:00:00 2001 From: Dmitry Zhelnin Date: Sun, 4 Sep 2022 22:22:09 +0300 Subject: [PATCH 42/59] TreeView: improve navigation with Left and Right keys --- src/Avalonia.Controls/TreeView.cs | 1 + src/Avalonia.Controls/TreeViewItem.cs | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 7359f3cade..d78f9c82ef 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -495,6 +495,7 @@ namespace Avalonia.Controls break; case NavigationDirection.Down: + case NavigationDirection.Right: if (from?.IsExpanded == true && intoChildren && from.ItemCount > 0) { result = (TreeViewItem)from.ItemContainerGenerator.ContainerFromIndex(0)!; diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index 2e3aa037c2..ada081b808 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -157,17 +157,26 @@ namespace Avalonia.Controls switch (e.Key) { case Key.Right: - if (Items != null && Items.Cast().Any()) + if (Items != null && Items.Cast().Any() && !IsExpanded) { IsExpanded = true; + e.Handled = true; } - - e.Handled = true; break; case Key.Left: - IsExpanded = false; - e.Handled = true; + if (Items is not null && Items.Cast().Any() && IsExpanded) + { + if (IsFocused) + { + IsExpanded = false; + } + else + { + FocusManager.Instance?.Focus(this, NavigationMethod.Directional); + } + e.Handled = true; + } break; } } From 9fc7be195aff85da4f35f844681a233efea94b62 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 8 Sep 2022 10:28:46 +0200 Subject: [PATCH 43/59] fix: CS8667 Nullability of reference types in type of --- samples/ControlCatalog/Converter/HexConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/Converter/HexConverter.cs b/samples/ControlCatalog/Converter/HexConverter.cs index 83a52212f6..31cce5ba67 100644 --- a/samples/ControlCatalog/Converter/HexConverter.cs +++ b/samples/ControlCatalog/Converter/HexConverter.cs @@ -7,7 +7,7 @@ namespace ControlCatalog.Converter; public class HexConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { var str = value?.ToString(); if (str == null) @@ -18,7 +18,7 @@ public class HexConverter : IValueConverter } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { try { From 11ebe62c03b11832e596dc44a66388f3beddf675 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Thu, 8 Sep 2022 13:08:04 +0300 Subject: [PATCH 44/59] Fix tests. --- .../ExpressionObserverBuilderTests_Method.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Method.cs b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Method.cs index 72e0ac5e57..0e499ff256 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Method.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Method.cs @@ -19,12 +19,9 @@ namespace Avalonia.Markup.UnitTests.Parsers public int MethodWithReturn() => 0; - public int MethodWithReturnAndParameters(int i) => i; + public int MethodWithReturnAndParameter(object i) => (int)i; public static void StaticMethod() { } - - public static void ManyParameters(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) { } - public static int ManyParametersWithReturnType(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) => 1; } [Fact] @@ -42,10 +39,8 @@ namespace Avalonia.Markup.UnitTests.Parsers [Theory] [InlineData(nameof(TestObject.MethodWithoutReturn), typeof(Action))] [InlineData(nameof(TestObject.MethodWithReturn), typeof(Func))] - [InlineData(nameof(TestObject.MethodWithReturnAndParameters), typeof(Func))] + [InlineData(nameof(TestObject.MethodWithReturnAndParameter), typeof(Func))] [InlineData(nameof(TestObject.StaticMethod), typeof(Action))] - [InlineData(nameof(TestObject.ManyParameters), typeof(Action))] - [InlineData(nameof(TestObject.ManyParametersWithReturnType), typeof(Func))] public async Task Should_Get_Method_WithCorrectDelegateType(string methodName, Type expectedType) { var data = new TestObject(); @@ -61,10 +56,10 @@ namespace Avalonia.Markup.UnitTests.Parsers public async Task Can_Call_Method_Returned_From_Observer() { var data = new TestObject(); - var observer = ExpressionObserverBuilder.Build(data, nameof(TestObject.MethodWithReturnAndParameters)); + var observer = ExpressionObserverBuilder.Build(data, nameof(TestObject.MethodWithReturnAndParameter)); var result = await observer.Take(1); - var callback = (Func)result; + var callback = (Func)result; Assert.Equal(1, callback(1)); From 426f4e1e64fb7367d102d4ac9511ef741a2becd8 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Thu, 8 Sep 2022 13:35:51 +0300 Subject: [PATCH 45/59] Fix Tests. --- .../Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs index 7931912649..f17903d24b 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs @@ -198,7 +198,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data public event PropertyChangedEventHandler PropertyChanged; public string Method() => Value = "Called"; - public string Method1(int i) => Value = $"Called {i}"; + public string Method1(object i) => Value = $"Called {i}"; public string Method2(int i, int j) => Value = $"Called {i},{j}"; public string Value { get; private set; } = "Not called"; From d59a998440002a91159cb184d85e9f8cece3d883 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Thu, 8 Sep 2022 15:32:58 +0300 Subject: [PATCH 46/59] Add test. --- .../Data/BindingTests_Method.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs index f17903d24b..d51d6122cd 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs @@ -141,6 +141,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data } } + [Fact] + public void Binding_Method_Preserves_Correct_Order() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + +