From a04af7b780867d730bdbdcf9cbaf0c54117ce1bb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 9 Feb 2023 11:23:23 +0100 Subject: [PATCH 01/10] Update ncrunch config. --- .ncrunch/Avalonia.UnitTests.v3.ncrunchproject | 5 +++++ .ncrunch/GpuInterop.v3.ncrunchproject | 5 +++++ Avalonia.Desktop.slnf | 1 + 3 files changed, 11 insertions(+) create mode 100644 .ncrunch/Avalonia.UnitTests.v3.ncrunchproject create mode 100644 .ncrunch/GpuInterop.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject new file mode 100644 index 0000000000..cff5044edf --- /dev/null +++ b/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + False + + \ No newline at end of file diff --git a/.ncrunch/GpuInterop.v3.ncrunchproject b/.ncrunch/GpuInterop.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/GpuInterop.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf index 3acd4bf9f2..741570061b 100644 --- a/Avalonia.Desktop.slnf +++ b/Avalonia.Desktop.slnf @@ -45,6 +45,7 @@ "tests\\Avalonia.Base.UnitTests\\Avalonia.Base.UnitTests.csproj", "tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj", "tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj", + "tests\\Avalonia.Controls.ItemsRepeater.UnitTests\\Avalonia.Controls.ItemsRepeater.UnitTests.csproj", "tests\\Avalonia.Controls.UnitTests\\Avalonia.Controls.UnitTests.csproj", "tests\\Avalonia.DesignerSupport.TestApp\\Avalonia.DesignerSupport.TestApp.csproj", "tests\\Avalonia.DesignerSupport.Tests\\Avalonia.DesignerSupport.Tests.csproj", From 035f4f0e55ba6115d6d064c5cbcb6eed91983ada Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 9 Feb 2023 12:02:18 +0100 Subject: [PATCH 02/10] Added failing integration tests for #10232. --- samples/IntegrationTestApp/MainWindow.axaml | 1 + .../ComboBoxTests.cs | 61 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index b116e4c789..4e5a8463df 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -70,6 +70,7 @@ Item 0 Item 1 + Wrap Selection diff --git a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs index abdb4e2dd8..8df7873582 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs @@ -47,7 +47,64 @@ namespace Avalonia.IntegrationTests.Appium } [PlatformFact(TestPlatforms.Windows)] - public void Can_Change_Selection_With_Keyboard() + public void Can_Change_Selection_With_Keyboard_When_Closed() + { + var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); + var wrap = _session.FindElementByAccessibilityId("ComboBoxWrapSelection"); + + if (wrap.GetIsChecked() != false) + wrap.Click(); + + _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click(); + + comboBox.SendKeys(Keys.ArrowDown); + Assert.Equal("Item 0", comboBox.GetComboBoxValue()); + + comboBox.SendKeys(Keys.ArrowDown); + Assert.Equal("Item 1", comboBox.GetComboBoxValue()); + + comboBox.SendKeys(Keys.ArrowDown); + Assert.Equal("Item 1", comboBox.GetComboBoxValue()); + + comboBox.SendKeys(Keys.ArrowUp); + Assert.Equal("Item 0", comboBox.GetComboBoxValue()); + + comboBox.SendKeys(Keys.ArrowUp); + Assert.Equal("Item 0", comboBox.GetComboBoxValue()); + } + + [PlatformFact(TestPlatforms.Windows)] + public void Can_Change_Wrapping_Selection_With_Keyboard_When_Closed() + { + var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); + var wrap = _session.FindElementByAccessibilityId("ComboBoxWrapSelection"); + + if (wrap.GetIsChecked() != true) + wrap.Click(); + + _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click(); + + comboBox.SendKeys(Keys.ArrowDown); + Assert.Equal("Item 0", comboBox.GetComboBoxValue()); + + comboBox.SendKeys(Keys.ArrowDown); + Assert.Equal("Item 1", comboBox.GetComboBoxValue()); + + comboBox.SendKeys(Keys.ArrowDown); + Assert.Equal("Item 0", comboBox.GetComboBoxValue()); + + comboBox.SendKeys(Keys.ArrowDown); + Assert.Equal("Item 1", comboBox.GetComboBoxValue()); + + comboBox.SendKeys(Keys.ArrowUp); + Assert.Equal("Item 0", comboBox.GetComboBoxValue()); + + comboBox.SendKeys(Keys.ArrowUp); + Assert.Equal("Item 1", comboBox.GetComboBoxValue()); + } + + [PlatformFact(TestPlatforms.Windows)] + public void Can_Change_Selection_When_Open_With_Keyboard() { var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); @@ -64,7 +121,7 @@ namespace Avalonia.IntegrationTests.Appium } [PlatformFact(TestPlatforms.Windows)] - public void Can_Change_Selection_With_Keyboard_From_Unselected() + public void Can_Change_Selection_When_Open_With_Keyboard_From_Unselected() { var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); From fdc65d1c4e41effe5913e73e6e90d4edfa5f1551 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 9 Feb 2023 12:04:10 +0100 Subject: [PATCH 03/10] Different selection logic when ComboBox closed. When the `ComboBox` is closed, then the existing `MoveSelection` method doesn't work because the popup containing the `ItemsPresenter` is not visible, and so has no realized items. Instead revert to simple index-based selection logic when the dropdown is closed. Fixes #10232 --- src/Avalonia.Controls/ComboBox.cs | 46 ++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index b7a298bb16..2d810236c3 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -1,10 +1,7 @@ using System; using System.Linq; using Avalonia.Automation.Peers; -using Avalonia.Reactive; -using Avalonia.Controls.Generators; -using Avalonia.Controls.Mixins; -using Avalonia.Controls.Presenters; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; @@ -12,8 +9,8 @@ using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Reactive; using Avalonia.VisualTree; -using Avalonia.Controls.Metadata; namespace Avalonia.Controls { @@ -482,7 +479,22 @@ namespace Avalonia.Controls { if (ItemCount >= 1) { - MoveSelection(NavigationDirection.Next, WrapSelection); + if (IsDropDownOpen) + { + MoveSelection(NavigationDirection.Next, WrapSelection); + } + else + { + var index = SelectedIndex + 1; + var count = ItemCount; + + if (WrapSelection) + index %= count; + else + index = Math.Min(index, count - 1); + + SelectedIndex = index; + } } } @@ -490,7 +502,27 @@ namespace Avalonia.Controls { if (ItemCount >= 1) { - MoveSelection(NavigationDirection.Previous, WrapSelection); + if (IsDropDownOpen) + { + MoveSelection(NavigationDirection.Previous, WrapSelection); + } + else + { + var index = SelectedIndex - 1; + var count = ItemCount; + + if (WrapSelection) + { + if (index < 0) + index += count; + } + else + { + index = Math.Max(index, 0); + } + + SelectedIndex = index; + } } } } From a331e1f86b843e37b54e9a3a7377170040124d04 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 9 Feb 2023 18:40:03 +0100 Subject: [PATCH 04/10] Account for enabled state of combo box items. --- src/Avalonia.Controls/ComboBox.cs | 65 +++++++++++++------------------ 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 2d810236c3..17a6ad7a09 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -1,10 +1,13 @@ using System; +using System.Diagnostics; using System.Linq; using Avalonia.Automation.Peers; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Selection; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; +using Avalonia.Controls.Utils; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; @@ -216,7 +219,7 @@ namespace Avalonia.Controls } else if (e.Key == Key.Up) { - SelectPrev(); + SelectPrevious(); e.Handled = true; } } @@ -247,7 +250,7 @@ namespace Avalonia.Controls if (e.Delta.Y < 0) SelectNext(); else - SelectPrev(); + SelectPrevious(); e.Handled = true; } @@ -475,53 +478,39 @@ namespace Avalonia.Controls } } - private void SelectNext() + private void SelectNext() => MoveSelection(SelectedIndex, 1, WrapSelection); + private void SelectPrevious() => MoveSelection(SelectedIndex, -1, WrapSelection); + + private void MoveSelection(int startIndex, int step, bool wrap) { - if (ItemCount >= 1) - { - if (IsDropDownOpen) - { - MoveSelection(NavigationDirection.Next, WrapSelection); - } - else - { - var index = SelectedIndex + 1; - var count = ItemCount; + static bool IsSelectable(object? o) => (o as AvaloniaObject)?.GetValue(IsEnabledProperty) ?? true; - if (WrapSelection) - index %= count; - else - index = Math.Min(index, count - 1); + var count = ItemCount; - SelectedIndex = index; - } - } - } - - private void SelectPrev() - { - if (ItemCount >= 1) + for (int i = startIndex + step; i != startIndex; i += step) { - if (IsDropDownOpen) - { - MoveSelection(NavigationDirection.Previous, WrapSelection); - } - else + if (i < 0 || i >= count) { - var index = SelectedIndex - 1; - var count = ItemCount; - - if (WrapSelection) + if (wrap) { - if (index < 0) - index += count; + if (i < 0) + i += count; + else if (i >= count) + i %= count; } else { - index = Math.Max(index, 0); + return; } + } - SelectedIndex = index; + var item = ItemsView[i]; + var container = ContainerFromIndex(i); + + if (IsSelectable(item) && IsSelectable(container)) + { + SelectedIndex = i; + break; } } } From 87709b960637a4bf12a170d95dd4f420eab31c09 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 9 Feb 2023 20:44:12 +0000 Subject: [PATCH 05/10] add IsScrollInertiaEnabled setter and getter --- src/Avalonia.Controls/ScrollViewer.cs | 16 ++++++++++++++++ src/Avalonia.Themes.Fluent/Controls/ListBox.xaml | 2 ++ 2 files changed, 18 insertions(+) diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index ab114da933..af874bd380 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -775,6 +775,22 @@ namespace Avalonia.Controls control.SetValue(VerticalScrollBarVisibilityProperty, value); } + /// + /// Gets whether scroll gestures should include inertia in their behavior and value. + /// + public static bool GetIsScrollInertiaEnabled(Control control) + { + return control.GetValue(IsScrollInertiaEnabledProperty); + } + + /// + /// Sets whether scroll gestures should include inertia in their behavior and value. + /// + public static void SetIsScrollInertiaEnabled(Control control, bool value) + { + control.SetValue(IsScrollInertiaEnabledProperty, value); + } + /// public void RegisterAnchorCandidate(Control element) { diff --git a/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml index 4b9fb76b8a..e2273cd487 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml @@ -19,6 +19,7 @@ + @@ -34,6 +35,7 @@ HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" + IsScrollInertiaEnabled="{TemplateBinding (ScrollViewer.IsScrollInertiaEnabled)}" AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"> Date: Fri, 10 Feb 2023 01:16:47 +0100 Subject: [PATCH 06/10] Fix CustomDrawingExampleControl crash on Direct2D --- .../Pages/CustomDrawingExampleControl.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs b/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs index 11e5e32cf1..938c45a4e2 100644 --- a/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs +++ b/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs @@ -59,10 +59,12 @@ namespace ControlCatalog.Pages }; StreamGeometry sg = new StreamGeometry(); - var cntx = sg.Open(); - cntx.BeginFigure(new Point(-25.0d, -10.0d), false); - cntx.ArcTo(new Point(25.0d, -10.0d), new Size(10.0d, 10.0d), 0.0d, false, SweepDirection.Clockwise); - cntx.EndFigure(true); + using (var cntx = sg.Open()) + { + cntx.BeginFigure(new Point(-25.0d, -10.0d), false); + cntx.ArcTo(new Point(25.0d, -10.0d), new Size(10.0d, 10.0d), 0.0d, false, SweepDirection.Clockwise); + cntx.EndFigure(true); + } _smileGeometry = sg.Clone(); } From 3568e60841a4cd2229a1a1b22d8dd1cfa4392d1d Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Fri, 10 Feb 2023 01:20:47 +0100 Subject: [PATCH 07/10] Set WindowsInteropTest platform target to x64 --- .../interop/WindowsInteropTest/WindowsInteropTest.csproj | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj index 1643ca3ee2..95f77f6df9 100644 --- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj +++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj @@ -2,7 +2,7 @@ WinExe net461 - + x64 true true @@ -10,9 +10,6 @@ - - {d0a739b9-3c68-4ba6-a328-41606954b6bd} - ControlCatalog - + From 7d879cf6febe500a6f5f7fcda786b88d075abbf0 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Fri, 10 Feb 2023 13:00:25 +0100 Subject: [PATCH 08/10] Added Win32PlatformOptions.ShouldRenderOnUIThread Fixes WPF interop --- samples/interop/WindowsInteropTest/Program.cs | 11 +++++++-- .../Rendering/DefaultRenderTimer.cs | 5 ++-- src/Avalonia.Base/Rendering/IRenderLoop.cs | 5 +++- src/Avalonia.Base/Rendering/RenderLoop.cs | 1 + .../Rendering/UiThreadRenderTimer.cs | 9 ++++++- .../Wpf/WpfTopLevelImpl.cs | 9 +------ src/Windows/Avalonia.Win32/Win32Platform.cs | 24 +++++++++++++------ 7 files changed, 42 insertions(+), 22 deletions(-) diff --git a/samples/interop/WindowsInteropTest/Program.cs b/samples/interop/WindowsInteropTest/Program.cs index fac06d74b0..c2d30c67bb 100644 --- a/samples/interop/WindowsInteropTest/Program.cs +++ b/samples/interop/WindowsInteropTest/Program.cs @@ -1,5 +1,4 @@ using System; -using Avalonia.Controls; using ControlCatalog; using Avalonia; @@ -15,7 +14,15 @@ namespace WindowsInteropTest { System.Windows.Forms.Application.EnableVisualStyles(); System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false); - AppBuilder.Configure().UseWin32().UseDirect2D1().SetupWithoutStarting(); + AppBuilder.Configure() + .UseWin32() + .UseDirect2D1() + .With(new Win32PlatformOptions + { + UseWindowsUIComposition = false, + ShouldRenderOnUIThread = true // necessary for WPF + }) + .SetupWithoutStarting(); System.Windows.Forms.Application.Run(new SelectorForm()); } } diff --git a/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs b/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs index d0d3dd9715..7b0fecf675 100644 --- a/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs +++ b/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs @@ -1,6 +1,4 @@ using System; -using System.Diagnostics; -using System.Threading.Tasks; using Avalonia.Platform; namespace Avalonia.Rendering @@ -59,7 +57,8 @@ namespace Avalonia.Rendering } } - public bool RunsInBackground => true; + /// + public virtual bool RunsInBackground => true; /// /// Starts the timer. diff --git a/src/Avalonia.Base/Rendering/IRenderLoop.cs b/src/Avalonia.Base/Rendering/IRenderLoop.cs index e500ecdf8b..ebe683949d 100644 --- a/src/Avalonia.Base/Rendering/IRenderLoop.cs +++ b/src/Avalonia.Base/Rendering/IRenderLoop.cs @@ -27,7 +27,10 @@ namespace Avalonia.Rendering /// /// The update task. void Remove(IRenderLoopTask i); - + + /// + /// Indicates if the rendering is done on a non-UI thread. + /// bool RunsInBackground { get; } } } diff --git a/src/Avalonia.Base/Rendering/RenderLoop.cs b/src/Avalonia.Base/Rendering/RenderLoop.cs index 1f58ca3827..185f44d29a 100644 --- a/src/Avalonia.Base/Rendering/RenderLoop.cs +++ b/src/Avalonia.Base/Rendering/RenderLoop.cs @@ -87,6 +87,7 @@ namespace Avalonia.Rendering } } + /// public bool RunsInBackground => Timer.RunsInBackground; private void TimerTick(TimeSpan time) diff --git a/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs b/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs index 1bbf804b5f..7f2eedc98c 100644 --- a/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs +++ b/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs @@ -8,13 +8,20 @@ namespace Avalonia.Rendering /// /// Render timer that ticks on UI thread. Useful for debugging or bootstrapping on new platforms /// - public class UiThreadRenderTimer : DefaultRenderTimer { + /// + /// Initializes a new instance of the class. + /// + /// The number of frames per second at which the loop should run. public UiThreadRenderTimer(int framesPerSecond) : base(framesPerSecond) { } + /// + public override bool RunsInBackground => false; + + /// protected override IDisposable StartCore(Action tick) { bool cancelled = false; diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 2d0f351d58..13eae1992c 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -45,13 +45,6 @@ namespace Avalonia.Win32.Interop.Wpf ((FrameworkElement)PlatformImpl)?.InvalidateMeasure(); } - protected override void HandleResized(Size clientSize, PlatformResizeReason reason) - { - ClientSize = clientSize; - LayoutManager.ExecuteLayoutPass(); - Renderer?.Resized(clientSize); - } - public Size AllocatedSize => ClientSize; } @@ -223,7 +216,7 @@ namespace Avalonia.Win32.Interop.Wpf (Key)e.Key, GetModifiers(null))); - protected override void OnTextInput(TextCompositionEventArgs e) + protected override void OnTextInput(TextCompositionEventArgs e) => _ttl.Input?.Invoke(new RawTextInputEventArgs(_keyboard, (uint) e.Timestamp, _inputRoot, e.Text)); void ITopLevelImpl.SetCursor(ICursorImpl cursor) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index f16a1ca8cf..c34be9008a 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -6,12 +6,10 @@ using System.IO; using Avalonia.Reactive; using System.Runtime.InteropServices; using System.Threading; -using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; -using Avalonia.Media; using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; @@ -20,7 +18,6 @@ using Avalonia.Threading; using Avalonia.Utilities; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; -using Avalonia.Win32.WinRT; using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia @@ -68,6 +65,7 @@ namespace Avalonia /// /// Render Avalonia to a Texture inside the Windows.UI.Composition tree. + /// This setting is true by default. /// /// /// Supported on Windows 10 build 16299 and above. Ignored on other versions. @@ -88,9 +86,19 @@ namespace Avalonia /// This is only recommended if low input latency is desirable, and there is no need for the transparency /// and stylings / blurrings offered by
/// This is mutually exclusive with - /// which if active will override this setting. + /// which if active will override this setting. + /// This setting is false by default. /// - public bool UseLowLatencyDxgiSwapChain { get; set; } = false; + public bool UseLowLatencyDxgiSwapChain { get; set; } + + /// + /// Render directly on the UI thread instead of using a dedicated render thread. + /// Only applicable if both and + /// are false. + /// This setting is only recommended for interop with systems that must render on the UI thread, such as WPF. + /// This setting is false by default. + /// + public bool ShouldRenderOnUIThread { get; set; } /// /// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu @@ -128,7 +136,7 @@ namespace Avalonia.Win32 internal static bool UseOverlayPopups => Options.OverlayPopups; public static Win32PlatformOptions Options { get; private set; } - + internal static Compositor Compositor { get; private set; } public static void Initialize() @@ -139,6 +147,8 @@ namespace Avalonia.Win32 public static void Initialize(Win32PlatformOptions options) { Options = options; + var renderTimer = options.ShouldRenderOnUIThread ? new UiThreadRenderTimer(60) : new DefaultRenderTimer(60); + AvaloniaLocator.CurrentMutable .Bind().ToSingleton() .Bind().ToConstant(CursorFactory.Instance) @@ -146,7 +156,7 @@ namespace Avalonia.Win32 .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToConstant(new RenderLoop()) - .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToConstant(renderTimer) .Bind().ToConstant(s_instance) .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control) { From 1c9032dd7228ae2c2ae520dae690f0851fda1a33 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Fri, 10 Feb 2023 13:04:14 +0100 Subject: [PATCH 09/10] Win32 tray icon: avoid HIcon access when removing Prevents a crash when the TrayIconImpl finalizer runs after the System.Drawing.Icon has been disposed --- src/Windows/Avalonia.Win32/TrayIconImpl.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index ab70d77a09..fe920ff1a1 100644 --- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs +++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs @@ -81,19 +81,18 @@ namespace Avalonia.Win32 private void UpdateIcon(bool remove = false) { - var iconData = new NOTIFYICONDATA() + var iconData = new NOTIFYICONDATA { hWnd = Win32Platform.Instance.Handle, - uID = _uniqueId, - uFlags = NIF.TIP | NIF.MESSAGE, - uCallbackMessage = (int)CustomWindowsMessage.WM_TRAYMOUSE, - hIcon = _icon?.HIcon ?? s_emptyIcon, - szTip = _tooltipText ?? "" + uID = _uniqueId }; if (!remove) { - iconData.uFlags |= NIF.ICON; + iconData.uFlags = NIF.TIP | NIF.MESSAGE | NIF.ICON; + iconData.uCallbackMessage = (int)CustomWindowsMessage.WM_TRAYMOUSE; + iconData.hIcon = _icon?.HIcon ?? s_emptyIcon; + iconData.szTip = _tooltipText ?? ""; if (!_iconAdded) { @@ -107,6 +106,7 @@ namespace Avalonia.Win32 } else { + iconData.uFlags = 0; Shell_NotifyIcon(NIM.DELETE, iconData); _iconAdded = false; } From 23d999c2eb1d5ce39e42d1c8ce36243d7d30d47a Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Fri, 10 Feb 2023 19:18:18 +0200 Subject: [PATCH 10/10] Make all Render overrides sealed (#10299) --- src/Avalonia.Controls/Border.cs | 2 +- src/Avalonia.Controls/ExperimentalAcrylicBorder.cs | 2 +- src/Avalonia.Controls/Grid.cs | 2 +- src/Avalonia.Controls/Image.cs | 2 +- src/Avalonia.Controls/Panel.cs | 2 +- src/Avalonia.Controls/Presenters/ContentPresenter.cs | 2 +- src/Avalonia.Controls/Presenters/TextPresenter.cs | 2 +- src/Avalonia.Controls/Primitives/AccessText.cs | 5 ++--- src/Avalonia.Controls/Remote/RemoteWidget.cs | 2 +- src/Avalonia.Controls/Shapes/Shape.cs | 2 +- src/Avalonia.Controls/TextBlock.cs | 8 +++++++- src/Avalonia.Controls/TickBar.cs | 2 +- 12 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs index 1bb574acd2..78ba23c1dd 100644 --- a/src/Avalonia.Controls/Border.cs +++ b/src/Avalonia.Controls/Border.cs @@ -225,7 +225,7 @@ namespace Avalonia.Controls /// Renders the control. /// /// The drawing context. - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { _borderRenderHelper.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush, BoxShadow, BorderDashOffset, BorderLineCap, BorderLineJoin, BorderDashArray); diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs index e4487d29fa..e1f840672d 100644 --- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs +++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs @@ -80,7 +80,7 @@ namespace Avalonia.Controls _subscription?.Dispose(); } - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { if (context.PlatformImpl is IDrawingContextWithAcrylicLikeSupport idc) { diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 2501440ff2..ff9fb4e31d 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -3256,7 +3256,7 @@ namespace Avalonia.Controls /// /// UpdateRenderBounds. /// - public override void Render(DrawingContext drawingContext) + public sealed override void Render(DrawingContext drawingContext) { var grid = this.GetVisualParent(); diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index 2cf0fc3ec4..3e76835e92 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -73,7 +73,7 @@ namespace Avalonia.Controls /// Renders the control. /// /// The drawing context. - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { var source = Source; diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index 007d18c813..a7dc035459 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -68,7 +68,7 @@ namespace Avalonia.Controls /// Renders the visual to a . /// /// The drawing context. - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { var background = Background; if (background != null) diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 584dfea97f..be61bb18a1 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -534,7 +534,7 @@ namespace Avalonia.Controls.Presenters } /// - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { _borderRenderer.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush, BoxShadow); diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index f599511392..3481b1ecf3 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -388,7 +388,7 @@ namespace Avalonia.Controls.Presenters TextLayout.Draw(context, new Point(left, top)); } - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { var selectionStart = SelectionStart; var selectionEnd = SelectionEnd; diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs index 49e76d0728..ed3412bb45 100644 --- a/src/Avalonia.Controls/Primitives/AccessText.cs +++ b/src/Avalonia.Controls/Primitives/AccessText.cs @@ -60,10 +60,9 @@ namespace Avalonia.Controls.Primitives /// Renders the to a drawing context. /// /// The drawing context. - public override void Render(DrawingContext context) + protected internal override void RenderCore(DrawingContext context) { - base.Render(context); - + base.RenderCore(context); int underscore = Text?.IndexOf('_') ?? -1; if (underscore != -1 && ShowAccessKey) diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs index 5cefb0d89f..b9854ab837 100644 --- a/src/Avalonia.Controls/Remote/RemoteWidget.cs +++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs @@ -69,7 +69,7 @@ namespace Avalonia.Controls.Remote base.ArrangeCore(finalRect); } - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { if (_lastFrame != null && _lastFrame.Width != 0 && _lastFrame.Height != 0) { diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index c8bf95b3f7..461dc1c947 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -193,7 +193,7 @@ namespace Avalonia.Controls.Shapes set { SetValue(StrokeJoinProperty, value); } } - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { var geometry = RenderedGeometry; diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index ec31470126..df98d1073e 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -549,7 +549,13 @@ namespace Avalonia.Controls /// Renders the to a drawing context. /// /// The drawing context. - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) + { + RenderCore(context); + } + + // Workaround to seal Render method, we need to make so because AccessText was overriding Render method which is sealed now. + internal protected virtual void RenderCore(DrawingContext context) { var background = Background; diff --git a/src/Avalonia.Controls/TickBar.cs b/src/Avalonia.Controls/TickBar.cs index 4d902d3d5f..63fe9c9384 100644 --- a/src/Avalonia.Controls/TickBar.cs +++ b/src/Avalonia.Controls/TickBar.cs @@ -213,7 +213,7 @@ namespace Avalonia.Controls /// /// Brush that use to fill ticks is specified by Fill property. /// - public override void Render(DrawingContext dc) + public sealed override void Render(DrawingContext dc) { var size = new Size(Bounds.Width, Bounds.Height); var range = Maximum - Minimum;