From 571b7746eac6cc694aa586ec83d48ad5c9f4b9ac Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 22 Jan 2023 11:46:28 +0100 Subject: [PATCH 01/61] Added failing integration test. --- samples/IntegrationTestApp/MainWindow.axaml | 1 + .../IntegrationTestApp/MainWindow.axaml.cs | 2 ++ .../WindowTests_MacOS.cs | 24 ++++++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index b116e4c789..7b1f0eddce 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -139,6 +139,7 @@ Maximized FullScreen + Can Resize diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 3cd5350cce..087f25666b 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -66,11 +66,13 @@ namespace IntegrationTestApp var locationComboBox = this.GetControl("ShowWindowLocation"); var stateComboBox = this.GetControl("ShowWindowState"); var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null; + var canResizeCheckBox = this.GetControl("ShowWindowCanResize"); var owner = (Window)this.GetVisualRoot()!; var window = new ShowWindowTest { WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex, + CanResize = canResizeCheckBox.IsChecked.Value, }; if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index d9817ecdd1..06180be74a 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -323,11 +323,30 @@ namespace Avalonia.IntegrationTests.Appium secondaryWindow.GetChromeButtons().close.Click(); } - private IDisposable OpenWindow(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location) + [PlatformTheory(TestPlatforms.MacOS)] + [InlineData(ShowWindowMode.NonOwned)] + [InlineData(ShowWindowMode.Owned)] + [InlineData(ShowWindowMode.Modal)] + public void Window_Has_Disabled_Zoom_Button_When_CanResize_Is_False(ShowWindowMode mode) + { + using (OpenWindow(null, mode, WindowStartupLocation.Manual, canResize: false)) + { + var secondaryWindow = GetWindow("SecondaryWindow"); + var (_, _, zoomButton) = secondaryWindow.GetChromeButtons(); + Assert.False(zoomButton.Enabled); + } + } + + private IDisposable OpenWindow( + PixelSize? size, + ShowWindowMode mode, + WindowStartupLocation location, + bool canResize = true) { var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); + var canResizeCheckBox = _session.FindElementByAccessibilityId("ShowWindowCanResize"); var showButton = _session.FindElementByAccessibilityId("ShowWindow"); if (size.HasValue) @@ -338,6 +357,9 @@ namespace Avalonia.IntegrationTests.Appium locationComboBox.Click(); _session.FindElementByName(location.ToString()).SendClick(); + + if (canResizeCheckBox.GetIsChecked() != canResize) + canResizeCheckBox.Click(); return showButton.OpenWindowWithClick(); } From 2951b80c39601a8368cc902f76ef4f01bc9307c5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 4 Feb 2023 21:26:19 +0100 Subject: [PATCH 02/61] Add additional failing integration tests. For problems introduced in #10153. --- .../WindowTests.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 7bb991aae6..23381c2e58 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -30,9 +30,9 @@ namespace Avalonia.IntegrationTests.Appium [Theory] [MemberData(nameof(StartupLocationData))] - public void StartupLocation(Size? size, ShowWindowMode mode, WindowStartupLocation location) + public void StartupLocation(Size? size, ShowWindowMode mode, WindowStartupLocation location, bool canResize) { - using var window = OpenWindow(size, mode, location); + using var window = OpenWindow(size, mode, location, canResize: canResize); var info = GetWindowInfo(); if (size.HasValue) @@ -230,10 +230,10 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal(new Rgba32(255, 0, 0), centerColor); } - public static TheoryData StartupLocationData() + public static TheoryData StartupLocationData() { var sizes = new Size?[] { null, new Size(400, 300) }; - var data = new TheoryData(); + var data = new TheoryData(); foreach (var size in sizes) { @@ -243,7 +243,8 @@ namespace Avalonia.IntegrationTests.Appium { if (!(location == WindowStartupLocation.CenterOwner && mode == ShowWindowMode.NonOwned)) { - data.Add(size, mode, location); + data.Add(size, mode, location, true); + data.Add(size, mode, location, false); } } } @@ -311,14 +312,16 @@ namespace Avalonia.IntegrationTests.Appium Size? size, ShowWindowMode mode, WindowStartupLocation location = WindowStartupLocation.Manual, - WindowState state = Controls.WindowState.Normal) + WindowState state = Controls.WindowState.Normal, + bool canResize = true) { var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); var stateComboBox = _session.FindElementByAccessibilityId("ShowWindowState"); + var canResizeCheckBox = _session.FindElementByAccessibilityId("ShowWindowCanResize"); var showButton = _session.FindElementByAccessibilityId("ShowWindow"); - + if (size.HasValue) sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}"); @@ -331,6 +334,9 @@ namespace Avalonia.IntegrationTests.Appium stateComboBox.Click(); _session.FindElementByAccessibilityId($"ShowWindowState{state}").SendClick(); + if (canResizeCheckBox.GetIsChecked() != canResize) + canResizeCheckBox.Click(); + return showButton.OpenWindowWithClick(); } From f41749170277d8e79c7a5e16af1a1db083705008 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 4 Feb 2023 22:44:24 +0100 Subject: [PATCH 03/61] Speed up integration tests. Don't change combo boxes that are already set to the correct value. --- .../WindowTests.cs | 21 +++++++++++++------ .../WindowTests_MacOS.cs | 14 +++++++++---- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 23381c2e58..f3861ca267 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -325,14 +325,23 @@ namespace Avalonia.IntegrationTests.Appium if (size.HasValue) sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}"); - modeComboBox.Click(); - _session.FindElementByName(mode.ToString()).SendClick(); + if (modeComboBox.GetComboBoxValue() != mode.ToString()) + { + modeComboBox.Click(); + _session.FindElementByName(mode.ToString()).SendClick(); + } - locationComboBox.Click(); - _session.FindElementByName(location.ToString()).SendClick(); + if (locationComboBox.GetComboBoxValue() != location.ToString()) + { + locationComboBox.Click(); + _session.FindElementByName(location.ToString()).SendClick(); + } - stateComboBox.Click(); - _session.FindElementByAccessibilityId($"ShowWindowState{state}").SendClick(); + if (stateComboBox.GetComboBoxValue() != state.ToString()) + { + stateComboBox.Click(); + _session.FindElementByAccessibilityId($"ShowWindowState{state}").SendClick(); + } if (canResizeCheckBox.GetIsChecked() != canResize) canResizeCheckBox.Click(); diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 06180be74a..0839cbf183 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -352,11 +352,17 @@ namespace Avalonia.IntegrationTests.Appium if (size.HasValue) sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}"); - modeComboBox.Click(); - _session.FindElementByName(mode.ToString()).SendClick(); + if (modeComboBox.GetComboBoxValue() != mode.ToString()) + { + modeComboBox.Click(); + _session.FindElementByName(mode.ToString()).SendClick(); + } - locationComboBox.Click(); - _session.FindElementByName(location.ToString()).SendClick(); + if (locationComboBox.GetComboBoxValue() != location.ToString()) + { + locationComboBox.Click(); + _session.FindElementByName(location.ToString()).SendClick(); + } if (canResizeCheckBox.GetIsChecked() != canResize) canResizeCheckBox.Click(); From e7a6d6fbc690b96af757199c98ff1f324f3d75ec Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 12:00:48 +0100 Subject: [PATCH 04/61] Implement windowWillUseStandardFrame MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seems that `NSWindow.isZoomed` returns the wrong result unless you implement this? ¯\_(ツ)_/¯ --- native/Avalonia.Native/src/OSX/AvnWindow.mm | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index d3b7b4ede6..784072221d 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -223,6 +223,19 @@ } } +// From chromium: +// +// > The delegate or the window class should implement this method so that +// > -[NSWindow isZoomed] can be then determined by whether or not the current +// > window frame is equal to the zoomed frame. +// +// If we don't implement this, then isZoomed always returns true for a non- +// resizable window ¯\_(ツ)_/¯ +- (NSRect)windowWillUseStandardFrame:(NSWindow*)window + defaultFrame:(NSRect)newFrame { + return newFrame; +} + -(BOOL)canBecomeKeyWindow { if(_canBecomeKeyWindow) From 044a499db331b0d13e33d6427643bc1868470d30 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 14:27:09 +0100 Subject: [PATCH 05/61] Added additional failing integration tests. --- tests/Avalonia.IntegrationTests.Appium/WindowTests.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index f3861ca267..ec24caa18c 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -63,9 +63,9 @@ namespace Avalonia.IntegrationTests.Appium [Theory] [MemberData(nameof(WindowStateData))] - public void WindowState(Size? size, ShowWindowMode mode, WindowState state) + public void WindowState(Size? size, ShowWindowMode mode, WindowState state, bool canResize) { - using var window = OpenWindow(size, mode, state: state); + using var window = OpenWindow(size, mode, state: state, canResize: canResize); try { @@ -253,10 +253,10 @@ namespace Avalonia.IntegrationTests.Appium return data; } - public static TheoryData WindowStateData() + public static TheoryData WindowStateData() { var sizes = new Size?[] { null, new Size(400, 300) }; - var data = new TheoryData(); + var data = new TheoryData(); foreach (var size in sizes) { @@ -274,7 +274,8 @@ namespace Avalonia.IntegrationTests.Appium mode != ShowWindowMode.NonOwned) continue; - data.Add(size, mode, state); + data.Add(size, mode, state, true); + data.Add(size, mode, state, false); } } } From 38aaadf92db4e4957a3ccab2365937c215e3c89a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 15:16:59 +0100 Subject: [PATCH 06/61] Use custom zoom logic when !_canResize. `[NSWindow setIsZoomed]` requires that the window is resizable by the user in order to work; when `canResize == false` this is not that case. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index ce82f7d83f..af4f92524b 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -225,16 +225,12 @@ bool WindowImpl::IsZoomed() { } void WindowImpl::DoZoom() { - switch (_decorations) { - case SystemDecorationsNone: - case SystemDecorationsBorderOnly: - [Window setFrame:[Window screen].visibleFrame display:true]; - break; - - - case SystemDecorationsFull: - [Window performZoom:Window]; - break; + if (_decorations == SystemDecorationsNone || + _decorations == SystemDecorationsBorderOnly || + _canResize == false) { + [Window setFrame:[Window screen].visibleFrame display:true]; + } else { + [Window performZoom:Window]; } } From 5346344d12fa314c7317f90f03c8e798ee4ef6cc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 17:32:55 +0100 Subject: [PATCH 07/61] Don't call virtual method from ctor. And remove unneeded checks for already-existing `Window` (this method is always called from ctor). --- .../Avalonia.Native/src/OSX/WindowBaseImpl.mm | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 038e9a048c..4d3768a4a8 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -580,21 +580,12 @@ void WindowBaseImpl::CleanNSWindow() { } } -void WindowBaseImpl::CreateNSWindow(bool isDialog) { - if (isDialog) { - if (![Window isKindOfClass:[AvnPanel class]]) { - CleanNSWindow(); - - Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()]; - - [Window setHidesOnDeactivate:false]; - } +void WindowBaseImpl::CreateNSWindow(bool usePanel) { + if (usePanel) { + Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless]; + [Window setHidesOnDeactivate:false]; } else { - if (![Window isKindOfClass:[AvnWindow class]]) { - CleanNSWindow(); - - Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()]; - } + Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless]; } } From b609ba58a8e445ff68ddbcd64d9ab3d9d4f6cb6c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 17:43:02 +0100 Subject: [PATCH 08/61] Disable zoom button when CanResize == false. To do this more easily, merged `HideOrShowTrafficLights` into a virtual `UpdateStyle`. They were always called together, and really _must_ be called together; this enforces that. --- .../Avalonia.Native/src/OSX/WindowBaseImpl.h | 3 +- native/Avalonia.Native/src/OSX/WindowImpl.h | 3 +- native/Avalonia.Native/src/OSX/WindowImpl.mm | 37 +++++++++---------- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index 4c2758f6c6..afa3a2956f 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -106,8 +106,7 @@ BEGIN_INTERFACE_MAP() protected: virtual NSWindowStyleMask GetStyle(); - - void UpdateStyle(); + virtual void UpdateStyle(); private: void CreateNSWindow (bool isDialog); diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index 3861aaf170..e7e9b7f1d8 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -41,8 +41,6 @@ BEGIN_INTERFACE_MAP() WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl); - void HideOrShowTrafficLights (); - virtual HRESULT Show (bool activate, bool isDialog) override; virtual HRESULT SetEnabled (bool enable) override; @@ -101,6 +99,7 @@ BEGIN_INTERFACE_MAP() protected: virtual NSWindowStyleMask GetStyle() override; + void UpdateStyle () override; private: void OnInitialiseNSWindow(); diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index af4f92524b..ae3e1eaf1d 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -30,19 +30,6 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase OnInitialiseNSWindow(); } -void WindowImpl::HideOrShowTrafficLights() { - if (Window == nil) { - return; - } - - bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); - bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull; - - [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights]; -} - void WindowImpl::OnInitialiseNSWindow(){ [GetWindowProtocol() setCanBecomeKeyWindow:true]; @@ -67,8 +54,6 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) { WindowBaseImpl::Show(activate, isDialog); GetWindowState(&_actualWindowState); - HideOrShowTrafficLights(); - return SetWindowState(_lastWindowState); } } @@ -257,8 +242,6 @@ HRESULT WindowImpl::SetDecorations(SystemDecorations value) { UpdateStyle(); - HideOrShowTrafficLights(); - switch (_decorations) { case SystemDecorationsNone: [Window setHasShadow:NO]; @@ -415,9 +398,6 @@ HRESULT WindowImpl::SetExtendClientArea(bool enable) { } [GetWindowProtocol() setIsExtended:enable]; - - HideOrShowTrafficLights(); - UpdateStyle(); } @@ -608,3 +588,20 @@ NSWindowStyleMask WindowImpl::GetStyle() { } return s; } + +void WindowImpl::UpdateStyle() { + WindowBaseImpl::UpdateStyle(); + + if (Window == nil) { + return; + } + + bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); + bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull; + + [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights]; + [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights]; + [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights]; + [[Window standardWindowButton:NSWindowZoomButton] setEnabled:_canResize]; +} + From d40041f02dd8ae5e53074e3b4c3e47b420ff9457 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 19:13:59 +0100 Subject: [PATCH 09/61] GetStyle => abstract CalculateStyleMask. - Make naming more clear - it's not getting the style mask, it's calculating what it should be in order to update the mask - Make it abstract to prevent it being called from the ctor in future --- native/Avalonia.Native/src/OSX/PopupImpl.mm | 2 +- native/Avalonia.Native/src/OSX/WindowBaseImpl.h | 2 +- native/Avalonia.Native/src/OSX/WindowBaseImpl.mm | 10 +--------- native/Avalonia.Native/src/OSX/WindowImpl.h | 2 +- native/Avalonia.Native/src/OSX/WindowImpl.mm | 2 +- 5 files changed, 5 insertions(+), 13 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/PopupImpl.mm b/native/Avalonia.Native/src/OSX/PopupImpl.mm index 9820a9f052..972d03d08c 100644 --- a/native/Avalonia.Native/src/OSX/PopupImpl.mm +++ b/native/Avalonia.Native/src/OSX/PopupImpl.mm @@ -29,7 +29,7 @@ private: [Window setLevel:NSPopUpMenuWindowLevel]; } protected: - virtual NSWindowStyleMask GetStyle() override + virtual NSWindowStyleMask CalculateStyleMask() override { return NSWindowStyleMaskBorderless; } diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index afa3a2956f..93decef136 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -105,7 +105,7 @@ BEGIN_INTERFACE_MAP() virtual void BringToFront (); protected: - virtual NSWindowStyleMask GetStyle(); + virtual NSWindowStyleMask CalculateStyleMask() = 0; virtual void UpdateStyle(); private: diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 4d3768a4a8..59102e15a6 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -35,18 +35,14 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, lastSize = NSSize { 100, 100 }; lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX}; lastMinSize = NSSize { 0, 0 }; - lastMenu = nullptr; CreateNSWindow(usePanel); [Window setContentView:StandardContainer]; - [Window setStyleMask:NSWindowStyleMaskBorderless]; [Window setBackingType:NSBackingStoreBuffered]; - [Window setContentMinSize:lastMinSize]; [Window setContentMaxSize:lastMaxSize]; - [Window setOpaque:false]; } @@ -564,12 +560,8 @@ bool WindowBaseImpl::IsModal() { return false; } -NSWindowStyleMask WindowBaseImpl::GetStyle() { - return NSWindowStyleMaskBorderless; -} - void WindowBaseImpl::UpdateStyle() { - [Window setStyleMask:GetStyle()]; + [Window setStyleMask:CalculateStyleMask()]; } void WindowBaseImpl::CleanNSWindow() { diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index e7e9b7f1d8..9c684c77c4 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -98,7 +98,7 @@ BEGIN_INTERFACE_MAP() bool CanBecomeKeyWindow (); protected: - virtual NSWindowStyleMask GetStyle() override; + virtual NSWindowStyleMask CalculateStyleMask() override; void UpdateStyle () override; private: diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index ae3e1eaf1d..afd9c1a5ea 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -553,7 +553,7 @@ bool WindowImpl::IsOwned() { return _parent != nullptr; } -NSWindowStyleMask WindowImpl::GetStyle() { +NSWindowStyleMask WindowImpl::CalculateStyleMask() { unsigned long s = NSWindowStyleMaskBorderless; if(_actualWindowState == FullScreen) From 959b09c2434a285cead448fa8f22a6d282afb90d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 00:18:15 +0100 Subject: [PATCH 10/61] Skip flaky test for now. --- tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 0839cbf183..1933d10919 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -151,7 +151,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Normal", windowState.Text); } - [PlatformFact(TestPlatforms.MacOS)] + [PlatformFact(TestPlatforms.MacOS, Skip = "Flaky test, skip for now")] public void Does_Not_Switch_Space_From_FullScreen_To_Main_Desktop_When_FullScreen_Window_Clicked() { // Issue #9565 From 6a1910172c2a060f7ef67d875af03064db768d5f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 10:26:45 +0100 Subject: [PATCH 11/61] Move traffic lights logic into one place. --- native/Avalonia.Native/src/OSX/AvnWindow.mm | 4 ---- native/Avalonia.Native/src/OSX/WindowImpl.mm | 15 ++++++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index 784072221d..23abf1d53f 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -274,10 +274,6 @@ -(void) setEnabled:(bool)enable { _isEnabled = enable; - - [[self standardWindowButton:NSWindowCloseButton] setEnabled:enable]; - [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:enable]; - [[self standardWindowButton:NSWindowZoomButton] setEnabled:enable]; } -(void)becomeKeyWindow diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index afd9c1a5ea..e4bbe24cb8 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -599,9 +599,14 @@ void WindowImpl::UpdateStyle() { bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull; - [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowZoomButton] setEnabled:_canResize]; -} + NSButton* closeButton = [Window standardWindowButton:NSWindowCloseButton]; + NSButton* miniaturizeButton = [Window standardWindowButton:NSWindowMiniaturizeButton]; + NSButton* zoomButton = [Window standardWindowButton:NSWindowZoomButton]; + [closeButton setHidden:!hasTrafficLights]; + [closeButton setEnabled:_isEnabled]; + [miniaturizeButton setHidden:!hasTrafficLights]; + [miniaturizeButton setEnabled:_isEnabled]; + [zoomButton setHidden:!hasTrafficLights]; + [zoomButton setEnabled:_isEnabled && _canResize]; +} From a705f546bba1f3fb973aae2cdeb9ed8d08cf4f16 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 11:46:18 +0100 Subject: [PATCH 12/61] Enable resizing during fullscreen transition. macOS seems to tie resizing of the NSView inside the window to the resizable style mask of the window somehow. If we programmatically transition a non-resizable window to fullscreen, the inner NSView's size isn't changed, so we need to make the window resizable during the fullscreen transition. Makes the final two failing `WindowState` integration tests pass. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index e4bbe24cb8..47e83f8d56 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -146,11 +146,13 @@ bool WindowImpl::CanBecomeKeyWindow() void WindowImpl::StartStateTransition() { _transitioningWindowState = true; + UpdateStyle(); } void WindowImpl::EndStateTransition() { _transitioningWindowState = false; - + UpdateStyle(); + // Ensure correct order of child windows after fullscreen transition. BringToFront(); } @@ -573,7 +575,7 @@ NSWindowStyleMask WindowImpl::CalculateStyleMask() { case SystemDecorationsFull: s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable; - if (_canResize && _isEnabled) { + if ((_canResize && _isEnabled) || _transitioningWindowState) { s = s | NSWindowStyleMaskResizable; } break; From 50a368eaa15e7a5756eeda2ec2fdf69d43e9f6da Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 13:05:05 +0100 Subject: [PATCH 13/61] Skip flaky test for now. --- tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 1933d10919..47a471fafd 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -56,7 +56,7 @@ namespace Avalonia.IntegrationTests.Appium } } - [PlatformFact(TestPlatforms.MacOS)] + [PlatformFact(TestPlatforms.MacOS, Skip = "Flaky test, skip for now")] public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip() { var mainWindow = GetWindow("MainWindow"); From 784c380c604b6fd6a139abd7233aaf668661bfd0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 16:10:31 +0100 Subject: [PATCH 14/61] Don't change z-order of window exiting fullscreen. When a window exits fullscreen, its child windows need to be ordered, but we shouldn't touch the z-order of the window itself as this sometimes seemed to result in the parent window being shown over the child windows. Fixes flaky integration tests (hopefully). --- native/Avalonia.Native/src/OSX/WindowImpl.h | 1 + native/Avalonia.Native/src/OSX/WindowImpl.mm | 21 ++++++++++++------- .../WindowTests_MacOS.cs | 4 ++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index 9c684c77c4..29bb659039 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -102,6 +102,7 @@ protected: void UpdateStyle () override; private: + void ZOrderChildWindows(); void OnInitialiseNSWindow(); NSString *_lastTitle; }; diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 47e83f8d56..4510d135dc 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -119,14 +119,19 @@ void WindowImpl::BringToFront() } [Window invalidateShadow]; + ZOrderChildWindows(); + } +} + +void WindowImpl::ZOrderChildWindows() +{ + for(auto iterator = _children.begin(); iterator != _children.end(); iterator++) + { + auto window = (*iterator)->Window; - for(auto iterator = _children.begin(); iterator != _children.end(); iterator++) - { - auto window = (*iterator)->Window; - - // #9565: Only bring window to front if it's on the currently active space - if ([window isOnActiveSpace]) - (*iterator)->BringToFront(); + // #9565: Only bring window to front if it's on the currently active space + if ([window isOnActiveSpace]) { + (*iterator)->BringToFront(); } } } @@ -154,7 +159,7 @@ void WindowImpl::EndStateTransition() { UpdateStyle(); // Ensure correct order of child windows after fullscreen transition. - BringToFront(); + ZOrderChildWindows(); } SystemDecorations WindowImpl::Decorations() { diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 47a471fafd..0839cbf183 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -56,7 +56,7 @@ namespace Avalonia.IntegrationTests.Appium } } - [PlatformFact(TestPlatforms.MacOS, Skip = "Flaky test, skip for now")] + [PlatformFact(TestPlatforms.MacOS)] public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip() { var mainWindow = GetWindow("MainWindow"); @@ -151,7 +151,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Normal", windowState.Text); } - [PlatformFact(TestPlatforms.MacOS, Skip = "Flaky test, skip for now")] + [PlatformFact(TestPlatforms.MacOS)] public void Does_Not_Switch_Space_From_FullScreen_To_Main_Desktop_When_FullScreen_Window_Clicked() { // Issue #9565 From 1abb1abaf5f03ee87f66946acc3b2fc709a45d73 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 8 Feb 2023 10:42:17 +0100 Subject: [PATCH 15/61] Don't overwrite unrelated style mask bits. Fixes a problem where we were clearing the fullscreen flag erroneously during a fullscreen transition, and in general it's best to preserve flags we're not interested in controlling anyway. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 4510d135dc..cf1ee6943d 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -561,13 +561,15 @@ bool WindowImpl::IsOwned() { } NSWindowStyleMask WindowImpl::CalculateStyleMask() { - unsigned long s = NSWindowStyleMaskBorderless; + // Use the current style mask and only clear the flags we're going to be modifying. + unsigned long s = [Window styleMask] & + ~(NSWindowStyleMaskFullSizeContentView | + NSWindowStyleMaskTitled | + NSWindowStyleMaskClosable | + NSWindowStyleMaskResizable | + NSWindowStyleMaskMiniaturizable | + NSWindowStyleMaskTexturedBackground); - if(_actualWindowState == FullScreen) - { - s |= NSWindowStyleMaskFullScreen; - } - switch (_decorations) { case SystemDecorationsNone: s = s | NSWindowStyleMaskFullSizeContentView; From 339b1e92bdc95c8b07b6c7f410744719883ffafe Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 8 Feb 2023 10:46:04 +0100 Subject: [PATCH 16/61] Add hack for strange win32 behavior. Fixes two failing integration tests on win32. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 1e0d92d442..22b43b1c18 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1180,8 +1180,21 @@ namespace Avalonia.Win32 var y = monitorInfo.rcWork.top; var cx = Math.Abs(monitorInfo.rcWork.right - x); var cy = Math.Abs(monitorInfo.rcWork.bottom - y); + var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); - SetWindowPos(_hwnd, WindowPosZOrder.HWND_NOTOPMOST, x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW); + if (!style.HasFlag(WindowStyles.WS_SIZEFRAME)) + { + // When calling SetWindowPos on a maximized window it automatically adjusts + // for "hidden" borders which are placed offscreen, EVEN IF THE WINDOW HAS + // NO BORDERS, meaning that the window is placed wrong when we have CanResize + // == false. Account for this here. + var borderThickness = BorderThickness; + x -= (int)borderThickness.Left; + cx += (int)borderThickness.Left + (int)borderThickness.Right; + cy += (int)borderThickness.Bottom; + } + + SetWindowPos(_hwnd, WindowPosZOrder.HWND_NOTOPMOST, x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW | SetWindowPosFlags.SWP_FRAMECHANGED); } } } From 008cfce9a87cb64f33982fb90b10eb1e58ebaa1e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 8 Feb 2023 10:46:12 +0100 Subject: [PATCH 17/61] Update ncrunch config. --- .ncrunch/Avalonia.UnitTests.v3.ncrunchproject | 5 +++++ .ncrunch/GpuInterop.v3.ncrunchproject | 5 +++++ 2 files changed, 10 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 From 4b0ff3be639755567691c0964f39ee37796ac9f7 Mon Sep 17 00:00:00 2001 From: DJGosnell Date: Thu, 9 Feb 2023 15:05:46 -0500 Subject: [PATCH 18/61] Initial work for adding caches for SKTextBlobBuilder, SKRoundRect, SKFont usages. Updates SKPaintCache for new caching base class. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 43 +++++++---- src/Skia/Avalonia.Skia/GeometryImpl.cs | 4 +- .../Gpu/OpenGl/GlRenderTarget.cs | 4 +- .../Avalonia.Skia/PlatformRenderInterface.cs | 47 ++++++------ src/Skia/Avalonia.Skia/SKCacheBase.cs | 72 +++++++++++++++++++ src/Skia/Avalonia.Skia/SKFontCache.cs | 13 ++++ src/Skia/Avalonia.Skia/SKPaintCache.cs | 58 ++------------- src/Skia/Avalonia.Skia/SKRoundRectCache.cs | 26 +++++++ .../Avalonia.Skia/SKTextBlobBuilderCache.cs | 13 ++++ src/Skia/Avalonia.Skia/TextShaperImpl.cs | 6 +- 10 files changed, 194 insertions(+), 92 deletions(-) create mode 100644 src/Skia/Avalonia.Skia/SKCacheBase.cs create mode 100644 src/Skia/Avalonia.Skia/SKFontCache.cs create mode 100644 src/Skia/Avalonia.Skia/SKRoundRectCache.cs create mode 100644 src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index ba646c64ee..a29cbb1cc3 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -34,9 +34,9 @@ namespace Avalonia.Skia private GRContext _grContext; public GRContext GrContext => _grContext; private ISkiaGpu _gpu; - private readonly SKPaint _strokePaint = SKPaintCache.Get(); - private readonly SKPaint _fillPaint = SKPaintCache.Get(); - private readonly SKPaint _boxShadowPaint = SKPaintCache.Get(); + private readonly SKPaint _strokePaint = SKPaintCache.Shared.Get(); + private readonly SKPaint _fillPaint = SKPaintCache.Shared.Get(); + private readonly SKPaint _boxShadowPaint = SKPaintCache.Shared.Get(); private static SKShader s_acrylicNoiseShader; private readonly ISkiaGpuRenderSession _session; private bool _leased = false; @@ -186,13 +186,13 @@ namespace Avalonia.Skia var s = sourceRect.ToSKRect(); var d = destRect.ToSKRect(); - var paint = SKPaintCache.Get(); + var paint = SKPaintCache.Shared.Get(); paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity)); paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality(); paint.BlendMode = _currentBlendingMode.ToSKBlendMode(); drawableImage.Draw(this, s, d, paint); - SKPaintCache.ReturnReset(paint); + SKPaintCache.Shared.ReturnReset(paint); } /// @@ -535,7 +535,24 @@ namespace Avalonia.Skia { CheckLease(); Canvas.Save(); - Canvas.ClipRoundRect(clip.ToSKRoundRect(), antialias:true); + + // Get the rounded rectangle + var rc = clip.Rect.ToSKRect(); + + // Get a round rect from the cache. + var roundRect = SKRoundRectCache.Shared.Get(); + + roundRect.SetRectRadii(rc, + new[] + { + clip.RadiiTopLeft.ToSKPoint(), clip.RadiiTopRight.ToSKPoint(), + clip.RadiiBottomRight.ToSKPoint(), clip.RadiiBottomLeft.ToSKPoint(), + }); + + Canvas.ClipRoundRect(roundRect, antialias:true); + + // Should not need to reset as SetRectRadii overrides the values. + SKRoundRectCache.Shared.Return(roundRect); } /// @@ -569,9 +586,9 @@ namespace Avalonia.Skia try { // Return leased paints. - SKPaintCache.ReturnReset(_strokePaint); - SKPaintCache.ReturnReset(_fillPaint); - SKPaintCache.ReturnReset(_boxShadowPaint); + SKPaintCache.Shared.ReturnReset(_strokePaint); + SKPaintCache.Shared.ReturnReset(_fillPaint); + SKPaintCache.Shared.ReturnReset(_boxShadowPaint); if (_grContext != null) { @@ -633,7 +650,7 @@ namespace Avalonia.Skia { CheckLease(); - var paint = SKPaintCache.Get(); + var paint = SKPaintCache.Shared.Get(); Canvas.SaveLayer(paint); _maskStack.Push(CreatePaint(paint, mask, bounds.Size)); @@ -644,11 +661,11 @@ namespace Avalonia.Skia { CheckLease(); - var paint = SKPaintCache.Get(); + var paint = SKPaintCache.Shared.Get(); paint.BlendMode = SKBlendMode.DstIn; Canvas.SaveLayer(paint); - SKPaintCache.ReturnReset(paint); + SKPaintCache.Shared.ReturnReset(paint); PaintWrapper paintWrapper; using (paintWrapper = _maskStack.Pop()) @@ -656,7 +673,7 @@ namespace Avalonia.Skia Canvas.DrawPaint(paintWrapper.Paint); } // Return the paint wrapper's paint less the reset since the paint is already reset in the Dispose method above. - SKPaintCache.Return(paintWrapper.Paint); + SKPaintCache.Shared.Return(paintWrapper.Paint); Canvas.Restore(); diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 15a3ebff40..51386d2a45 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -81,12 +81,12 @@ namespace Avalonia.Skia } else { - var paint = SKPaintCache.Get(); + var paint = SKPaintCache.Shared.Get(); paint.IsStroke = true; paint.StrokeWidth = strokeWidth; paint.GetFillPath(EffectivePath, strokePath); - SKPaintCache.ReturnReset(paint); + SKPaintCache.Shared.ReturnReset(paint); _pathCache.Cache(strokePath, strokeWidth, strokePath.TightBounds.ToAvaloniaRect()); } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs index 4b3c7a016d..25e004f4ef 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs @@ -13,7 +13,7 @@ namespace Avalonia.Skia { private readonly GRContext _grContext; private IGlPlatformSurfaceRenderTarget _surface; - + private static readonly SKSurfaceProperties _surfaceProperties = new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal); public GlRenderTarget(GRContext grContext, IGlContext glContext, IGlPlatformSurface glSurface) { _grContext = grContext; @@ -92,7 +92,7 @@ namespace Avalonia.Skia var renderTarget = new GRBackendRenderTarget(size.Width, size.Height, samples, disp.StencilSize, glInfo); var surface = SKSurface.Create(_grContext, renderTarget, glSession.IsYFlipped ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft, - colorType, new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal)); + colorType, _surfaceProperties); success = true; diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index e795f3d304..8e9a19239b 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -76,13 +76,14 @@ namespace Avalonia.Skia } var fontRenderingEmSize = (float)glyphRun.FontRenderingEmSize; - var skFont = new SKFont(glyphTypeface.Typeface, fontRenderingEmSize) - { - Size = fontRenderingEmSize, - Edging = SKFontEdging.Alias, - Hinting = SKFontHinting.None, - LinearMetrics = true - }; + + var skFont = SKFontCache.Shared.Get(); + + skFont.Typeface = glyphTypeface.Typeface; + skFont.Size = fontRenderingEmSize; + skFont.Edging = SKFontEdging.Alias; + skFont.Hinting = SKFontHinting.None; + skFont.LinearMetrics = true; SKPath path = new SKPath(); @@ -101,6 +102,8 @@ namespace Avalonia.Skia currentX += glyphRun.GlyphInfos[i].GlyphAdvance; } + SKFontCache.Shared.Return(skFont); + return new StreamGeometryImpl(path); } @@ -224,20 +227,19 @@ namespace Avalonia.Skia var glyphTypefaceImpl = glyphTypeface as GlyphTypefaceImpl; - var font = new SKFont - { - LinearMetrics = true, - Subpixel = true, - Edging = SKFontEdging.SubpixelAntialias, - Hinting = SKFontHinting.Full, - Size = (float)fontRenderingEmSize, - Typeface = glyphTypefaceImpl.Typeface, - Embolden = (glyphTypefaceImpl.FontSimulations & FontSimulations.Bold) != 0, - SkewX = (glyphTypefaceImpl.FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0 - }; + var font = SKFontCache.Shared.Get(); + + font.LinearMetrics = true; + font.Subpixel = true; + font.Edging = SKFontEdging.SubpixelAntialias; + font.Hinting = SKFontHinting.Full; + font.Size = (float)fontRenderingEmSize; + font.Typeface = glyphTypefaceImpl.Typeface; + font.Embolden = (glyphTypefaceImpl.FontSimulations & FontSimulations.Bold) != 0; + font.SkewX = (glyphTypefaceImpl.FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0; - var builder = new SKTextBlobBuilder(); + var builder = SKTextBlobBuilderCache.Shared.Get(); var count = glyphInfos.Count; var runBuffer = builder.AllocatePositionedRun(font, count); @@ -245,6 +247,8 @@ namespace Avalonia.Skia var glyphSpan = runBuffer.GetGlyphSpan(); var positionSpan = runBuffer.GetPositionSpan(); + SKFontCache.Shared.Return(font); + var width = 0.0; for (int i = 0; i < count; i++) @@ -261,8 +265,11 @@ namespace Avalonia.Skia var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight; var height = glyphTypeface.Metrics.LineSpacing * scale; + var skTextBlob = builder.Build(); + + SKTextBlobBuilderCache.Shared.Return(builder); - return new GlyphRunImpl(builder.Build(), new Size(width, height), baselineOrigin); + return new GlyphRunImpl(skTextBlob, new Size(width, height), baselineOrigin); } } } diff --git a/src/Skia/Avalonia.Skia/SKCacheBase.cs b/src/Skia/Avalonia.Skia/SKCacheBase.cs new file mode 100644 index 0000000000..e1e78cd081 --- /dev/null +++ b/src/Skia/Avalonia.Skia/SKCacheBase.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Concurrent; +using SkiaSharp; + +namespace Avalonia.Skia +{ + /// + /// Cache base for Skia objects. + /// + internal abstract class SKCacheBase + where TCachedItem : IDisposable, new() + where TCache : new() + { + /// + /// Bag to hold the cached items. + /// + protected readonly ConcurrentBag Cache; + + /// + /// Shared cache. + /// + public static readonly TCache Shared = new TCache(); + + protected SKCacheBase() + { + Cache = new ConcurrentBag(); + } + + /// + /// Gets a cached item for usage. + /// + /// + /// If there is a available item in the cache, the cached item will be returned.. + /// Otherwise a new cached item will be created. + /// + /// + public TCachedItem Get() + { + if (!Cache.TryTake(out var item)) + { + item = new TCachedItem(); + } + + return item; + } + + /// + /// Returns the item for reuse later. + /// + /// + /// Do not use the item further. + /// Do not return the same item multiple times as that will break the cache. + /// + /// + public void Return(TCachedItem item) + { + Cache.Add(item); + } + + /// + /// Clears and disposes all cached items. + /// + public void Clear() + { + while (Cache.TryTake(out var item)) + { + item.Dispose(); + } + } + + } +} diff --git a/src/Skia/Avalonia.Skia/SKFontCache.cs b/src/Skia/Avalonia.Skia/SKFontCache.cs new file mode 100644 index 0000000000..348e085253 --- /dev/null +++ b/src/Skia/Avalonia.Skia/SKFontCache.cs @@ -0,0 +1,13 @@ +using System.Collections.Concurrent; +using SkiaSharp; + +namespace Avalonia.Skia +{ + /// + /// Cache for SKFonts. + /// + internal class SKFontCache : SKCacheBase + { + + } +} diff --git a/src/Skia/Avalonia.Skia/SKPaintCache.cs b/src/Skia/Avalonia.Skia/SKPaintCache.cs index 6588ab8da8..82c4dd23c7 100644 --- a/src/Skia/Avalonia.Skia/SKPaintCache.cs +++ b/src/Skia/Avalonia.Skia/SKPaintCache.cs @@ -6,46 +6,8 @@ namespace Avalonia.Skia /// /// Cache for SKPaints. /// - internal static class SKPaintCache + internal class SKPaintCache : SKCacheBase { - private static ConcurrentBag s_cachedPaints; - - static SKPaintCache() - { - s_cachedPaints = new ConcurrentBag(); - } - - /// - /// Gets a SKPaint for usage. - /// - /// - /// If a SKPaint is in the cache, that existing SKPaint will be returned. - /// Otherwise a new SKPaint will be created. - /// - /// - public static SKPaint Get() - { - if (!s_cachedPaints.TryTake(out var paint)) - { - paint = new SKPaint(); - } - - return paint; - } - - /// - /// Returns a SKPaint for reuse later. - /// - /// - /// Do not use the paint further. - /// Do not return the same paint multiple times as that will break the cache. - /// - /// - public static void Return(SKPaint paint) - { - s_cachedPaints.Add(paint); - } - /// /// Returns a SKPaint and resets it for reuse later. /// @@ -54,23 +16,11 @@ namespace Avalonia.Skia /// Do not return the same paint multiple times as that will break the cache. /// Uses SKPaint.Reset() for reuse later. /// - /// - public static void ReturnReset(SKPaint paint) + /// Paint to reset. + public void ReturnReset(SKPaint paint) { paint.Reset(); - s_cachedPaints.Add(paint); + Cache.Add(paint); } - - /// - /// Clears and disposes all cached paints. - /// - public static void Clear() - { - while (s_cachedPaints.TryTake(out var paint)) - { - paint.Dispose(); - } - } - } } diff --git a/src/Skia/Avalonia.Skia/SKRoundRectCache.cs b/src/Skia/Avalonia.Skia/SKRoundRectCache.cs new file mode 100644 index 0000000000..e164f97d6a --- /dev/null +++ b/src/Skia/Avalonia.Skia/SKRoundRectCache.cs @@ -0,0 +1,26 @@ +using System.Collections.Concurrent; +using SkiaSharp; + +namespace Avalonia.Skia +{ + /// + /// Cache for SKPaints. + /// + internal class SKRoundRectCache : SKCacheBase + { + /// + /// Returns a SKPaint and resets it for reuse later. + /// + /// + /// Do not use the rect further. + /// Do not return the same rect multiple times as that will break the cache. + /// Uses SKRoundRect.SetEmpty(); for reuse later. + /// + /// Rectangle to reset + public void ReturnReset(SKRoundRect rect) + { + rect.SetEmpty(); + Cache.Add(rect); + } + } +} diff --git a/src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs b/src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs new file mode 100644 index 0000000000..8c010ecb05 --- /dev/null +++ b/src/Skia/Avalonia.Skia/SKTextBlobBuilderCache.cs @@ -0,0 +1,13 @@ +using System.Collections.Concurrent; +using SkiaSharp; + +namespace Avalonia.Skia +{ + /// + /// Cache for SKTextBlobBuilder. + /// + internal class SKTextBlobBuilderCache : SKCacheBase + { + + } +} diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index e1a6b93692..a21038839c 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -1,5 +1,6 @@ using System; using System.Buffers; +using System.Collections.Concurrent; using System.Globalization; using System.Runtime.InteropServices; using Avalonia.Media.TextFormatting; @@ -13,6 +14,7 @@ namespace Avalonia.Skia { internal class TextShaperImpl : ITextShaperImpl { + private static readonly ConcurrentDictionary s_cachedLanguage = new(); public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options) { var textSpan = text.Span; @@ -33,7 +35,9 @@ namespace Avalonia.Skia buffer.Direction = (bidiLevel & 1) == 0 ? Direction.LeftToRight : Direction.RightToLeft; - buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); + var usedCulture = culture ?? CultureInfo.CurrentCulture; + + buffer.Language = s_cachedLanguage.GetOrAdd(usedCulture.LCID, i => new Language(usedCulture)); var font = ((GlyphTypefaceImpl)typeface).Font; From b11786424daa1eafb4bb53edf83b57e4daca4117 Mon Sep 17 00:00:00 2001 From: DJGosnell Date: Thu, 9 Feb 2023 16:53:40 -0500 Subject: [PATCH 19/61] Cached round SKRoundRects created with DrawingContextImpl.DrawRectangle --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 34 +++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index a29cbb1cc3..2bb6f1dc7e 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -315,15 +315,20 @@ namespace Avalonia.Skia var rc = rect.Rect.ToSKRect(); var isRounded = rect.IsRounded; var needRoundRect = rect.IsRounded; - using var skRoundRect = needRoundRect ? new SKRoundRect() : null; + SKRoundRect skRoundRect = null; if (needRoundRect) + { + skRoundRect = SKRoundRectCache.Shared.Get(); skRoundRect.SetRectRadii(rc, new[] { - rect.RadiiTopLeft.ToSKPoint(), rect.RadiiTopRight.ToSKPoint(), - rect.RadiiBottomRight.ToSKPoint(), rect.RadiiBottomLeft.ToSKPoint(), + rect.RadiiTopLeft.ToSKPoint(), + rect.RadiiTopRight.ToSKPoint(), + rect.RadiiBottomRight.ToSKPoint(), + rect.RadiiBottomLeft.ToSKPoint(), }); + } if (material != null) { @@ -332,6 +337,7 @@ namespace Avalonia.Skia if (isRounded) { Canvas.DrawRoundRect(skRoundRect, paint.Paint); + SKRoundRectCache.Shared.Return(skRoundRect); } else { @@ -356,14 +362,19 @@ namespace Avalonia.Skia var rc = rect.Rect.ToSKRect(); var isRounded = rect.IsRounded; var needRoundRect = rect.IsRounded || (boxShadows.HasInsetShadows); - using var skRoundRect = needRoundRect ? new SKRoundRect() : null; + SKRoundRect skRoundRect = null; if (needRoundRect) + { + skRoundRect = SKRoundRectCache.Shared.Get(); skRoundRect.SetRectRadii(rc, new[] { - rect.RadiiTopLeft.ToSKPoint(), rect.RadiiTopRight.ToSKPoint(), - rect.RadiiBottomRight.ToSKPoint(), rect.RadiiBottomLeft.ToSKPoint(), + rect.RadiiTopLeft.ToSKPoint(), + rect.RadiiTopRight.ToSKPoint(), + rect.RadiiBottomRight.ToSKPoint(), + rect.RadiiBottomLeft.ToSKPoint(), }); + } foreach (var boxShadow in boxShadows) { @@ -378,7 +389,8 @@ namespace Avalonia.Skia Canvas.Save(); if (isRounded) { - using var shadowRect = new SKRoundRect(skRoundRect); + var shadowRect = SKRoundRectCache.Shared.Get(); + shadowRect.SetRectRadii(skRoundRect!.Rect, skRoundRect.Radii); if (spread != 0) shadowRect.Inflate(spread, spread); Canvas.ClipRoundRect(skRoundRect, @@ -388,6 +400,7 @@ namespace Avalonia.Skia Transform = oldTransform * Matrix.CreateTranslation(boxShadow.OffsetX, boxShadow.OffsetY); Canvas.DrawRoundRect(shadowRect, shadow.Paint); Transform = oldTransform; + SKRoundRectCache.Shared.Return(shadowRect); } else { @@ -433,7 +446,8 @@ namespace Avalonia.Skia var outerRect = AreaCastingShadowInHole(rc, (float)boxShadow.Blur, spread, offsetX, offsetY); Canvas.Save(); - using var shadowRect = new SKRoundRect(skRoundRect); + var shadowRect = SKRoundRectCache.Shared.Get(); + shadowRect.SetRectRadii(skRoundRect!.Rect, skRoundRect.Radii); if (spread != 0) shadowRect.Deflate(spread, spread); Canvas.ClipRoundRect(skRoundRect, @@ -445,6 +459,7 @@ namespace Avalonia.Skia Canvas.DrawRoundRectDifference(outerRRect, shadowRect, shadow.Paint); Transform = oldTransform; Canvas.Restore(); + SKRoundRectCache.Shared.Return(shadowRect); } } } @@ -466,6 +481,9 @@ namespace Avalonia.Skia } } } + + if(isRounded) + SKRoundRectCache.Shared.Return(skRoundRect); } /// From 416ef6b601462f2eca229cfec1dd3481a8098aaa Mon Sep 17 00:00:00 2001 From: DJGosnell Date: Fri, 10 Feb 2023 18:45:20 -0500 Subject: [PATCH 20/61] Added two GetAndSetRadii methods. Added documentaiton. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 16 +---- src/Skia/Avalonia.Skia/SKRoundRectCache.cs | 76 +++++++++++++++++++- 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 2bb6f1dc7e..eededb2836 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -365,15 +365,7 @@ namespace Avalonia.Skia SKRoundRect skRoundRect = null; if (needRoundRect) { - skRoundRect = SKRoundRectCache.Shared.Get(); - skRoundRect.SetRectRadii(rc, - new[] - { - rect.RadiiTopLeft.ToSKPoint(), - rect.RadiiTopRight.ToSKPoint(), - rect.RadiiBottomRight.ToSKPoint(), - rect.RadiiBottomLeft.ToSKPoint(), - }); + skRoundRect = SKRoundRectCache.Shared.GetAndSetRadii(rc, rect); } foreach (var boxShadow in boxShadows) @@ -389,8 +381,7 @@ namespace Avalonia.Skia Canvas.Save(); if (isRounded) { - var shadowRect = SKRoundRectCache.Shared.Get(); - shadowRect.SetRectRadii(skRoundRect!.Rect, skRoundRect.Radii); + var shadowRect = SKRoundRectCache.Shared.GetAndSetRadii(skRoundRect!.Rect, skRoundRect.Radii); if (spread != 0) shadowRect.Inflate(spread, spread); Canvas.ClipRoundRect(skRoundRect, @@ -446,8 +437,7 @@ namespace Avalonia.Skia var outerRect = AreaCastingShadowInHole(rc, (float)boxShadow.Blur, spread, offsetX, offsetY); Canvas.Save(); - var shadowRect = SKRoundRectCache.Shared.Get(); - shadowRect.SetRectRadii(skRoundRect!.Rect, skRoundRect.Radii); + var shadowRect = SKRoundRectCache.Shared.GetAndSetRadii(skRoundRect!.Rect, skRoundRect.Radii); if (spread != 0) shadowRect.Deflate(spread, spread); Canvas.ClipRoundRect(skRoundRect, diff --git a/src/Skia/Avalonia.Skia/SKRoundRectCache.cs b/src/Skia/Avalonia.Skia/SKRoundRectCache.cs index e164f97d6a..8de9e65553 100644 --- a/src/Skia/Avalonia.Skia/SKRoundRectCache.cs +++ b/src/Skia/Avalonia.Skia/SKRoundRectCache.cs @@ -1,13 +1,73 @@ -using System.Collections.Concurrent; +using System.Buffers; +using System.Collections.Concurrent; +using System.Collections.Generic; using SkiaSharp; namespace Avalonia.Skia { /// - /// Cache for SKPaints. + /// Cache for SKRoundRectCache. /// internal class SKRoundRectCache : SKCacheBase { + /// + /// Cache for points to use for setting the radii. + /// + private readonly ConcurrentBag _radiiCache = new(); + + /// + /// Gets a cached SKRoundRect and sets it with the passed rectangle and Radii. + /// + /// Rectangle size to set the cached rectangle to. + /// Rounded rectangle to copy the radii from. + /// Configured rounded rectangle + public SKRoundRect GetAndSetRadii(in SKRect rectangle, in RoundedRect roundedRect) + { + if (!Cache.TryTake(out var item)) + { + item = new SKRoundRect(); + } + + // Try and acquire a cached point array. + if (!_radiiCache.TryTake(out var skArray)) + { + skArray = new SKPoint[4]; + } + + skArray[0].X = (float)roundedRect.RadiiTopLeft.X; + skArray[0].Y = (float)roundedRect.RadiiTopLeft.Y; + skArray[1].X = (float)roundedRect.RadiiTopRight.X; + skArray[1].Y = (float)roundedRect.RadiiTopRight.Y; + skArray[2].X = (float)roundedRect.RadiiBottomRight.X; + skArray[2].Y = (float)roundedRect.RadiiBottomRight.Y; + skArray[3].X = (float)roundedRect.RadiiBottomLeft.X; + skArray[3].Y = (float)roundedRect.RadiiBottomLeft.Y; + + item.SetRectRadii(rectangle, skArray); + + // Add the array back to the cache. + _radiiCache.Add(skArray); + + return item; + } + + /// + /// Gets a cached SKRoundRect and sets it with the passed rectangle and Radii. + /// + /// Rectangle size to set the cached rectangle to. + /// point array of radii. + /// Configured rounded rectangle + public SKRoundRect GetAndSetRadii(in SKRect rectangle, in SKPoint[] radii) + { + if (!Cache.TryTake(out var item)) + { + item = new SKRoundRect(); + } + + item.SetRectRadii(rectangle, radii); + + return item; + } /// /// Returns a SKPaint and resets it for reuse later. /// @@ -22,5 +82,17 @@ namespace Avalonia.Skia rect.SetEmpty(); Cache.Add(rect); } + + /// + /// Clears and disposes all cached items. + /// + public new void Clear() + { + base.Clear(); + + // Clear out the cache of SKPoint arrays. + _radiiCache.Clear(); + } + } } From 0d32343410c615809d87f1a06b1c6349f4b95b1a Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 8 Feb 2023 10:13:26 +0100 Subject: [PATCH 21/61] Nullability annotations for Avalonia.Win32 --- .../Platform/IPlatformThreadingInterface.cs | 2 +- .../Platform/Storage/FilePickerFileType.cs | 4 +- src/Avalonia.Base/Rendering/IRenderTimer.cs | 1 - .../Platform/IPlatformLifetimeEventsImpl.cs | 2 +- src/Avalonia.Controls/Platform/IWindowImpl.cs | 8 +- src/Avalonia.Controls/WindowBase.cs | 2 +- src/Windows/Avalonia.Win32/AngleOptions.cs | 2 +- .../AutomationNode.ExpandCollapse.cs | 2 - .../Automation/AutomationNode.RangeValue.cs | 2 - .../Automation/AutomationNode.Scroll.cs | 2 - .../Automation/AutomationNode.Selection.cs | 8 +- .../Automation/AutomationNode.Toggle.cs | 2 - .../Automation/AutomationNode.Value.cs | 2 - .../Automation/AutomationNode.cs | 10 +- .../Automation/RootAutomationNode.cs | 2 - .../Avalonia.Win32/Avalonia.Win32.csproj | 4 +- .../Avalonia.Win32/ClipboardFormats.cs | 30 +-- src/Windows/Avalonia.Win32/ClipboardImpl.cs | 18 +- src/Windows/Avalonia.Win32/DataObject.cs | 37 ++-- .../Avalonia.Win32/DirectX/DirectXStructs.cs | 8 - .../Avalonia.Win32/DirectX/DxgiConnection.cs | 28 +-- .../DirectX/DxgiRenderTarget.cs | 41 ++-- .../Avalonia.Win32/FramebufferManager.cs | 2 - .../Avalonia.Win32/Input/Imm32InputMethod.cs | 78 +++---- .../Input/WindowsMouseDevice.cs | 9 +- .../Interop/Automation/IGridProvider.cs | 3 +- .../Automation/IRawElementProviderFragment.cs | 2 - .../IRawElementProviderFragmentRoot.cs | 5 +- .../Automation/IRawElementProviderSimple.cs | 2 - .../Automation/IRawElementProviderSimple2.cs | 5 +- .../Automation/ISelectionItemProvider.cs | 2 - .../Interop/Automation/IValueProvider.cs | 3 - .../Interop/Automation/UiaCoreProviderApi.cs | 10 +- .../Interop/Automation/UiaCoreTypesApi.cs | 4 +- .../Avalonia.Win32/Interop/TaskBarList.cs | 15 +- .../Interop/UnmanagedMethods.cs | 54 ++--- .../Avalonia.Win32/NonPumpingSyncContext.cs | 19 +- .../Avalonia.Win32/OffscreenParentWindow.cs | 9 +- src/Windows/Avalonia.Win32/OleContext.cs | 12 +- src/Windows/Avalonia.Win32/OleDataObject.cs | 17 +- src/Windows/Avalonia.Win32/OleDropTarget.cs | 43 ++-- .../OpenGl/Angle/AngleD3DTextureFeature.cs | 6 +- .../OpenGl/Angle/AngleEglInterface.cs | 2 +- .../Angle/AngleExternalD3D11Texture2D.cs | 30 ++- .../Angle/AngleExternalObjectsFeature.cs | 6 +- .../OpenGl/Angle/AngleWin32EglDisplay.cs | 3 +- .../Angle/AngleWin32PlatformGraphics.cs | 16 +- .../Avalonia.Win32/OpenGl/WglContext.cs | 16 +- .../Avalonia.Win32/OpenGl/WglDisplay.cs | 45 ++-- .../OpenGl/WglGdiResourceManager.cs | 122 ++++++----- .../OpenGl/WglPlatformOpenGlInterface.cs | 18 +- .../OpenGl/WglRestoreContext.cs | 4 +- src/Windows/Avalonia.Win32/PopupImpl.cs | 15 +- src/Windows/Avalonia.Win32/ScreenImpl.cs | 19 +- src/Windows/Avalonia.Win32/TrayIconImpl.cs | 32 ++- src/Windows/Avalonia.Win32/Win32GlManager.cs | 17 +- .../Avalonia.Win32/Win32NativeControlHost.cs | 42 ++-- .../Win32NativeToManagedMenuExporter.cs | 9 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 50 ++--- .../Avalonia.Win32/Win32PlatformSettings.cs | 8 +- .../Avalonia.Win32/Win32StorageProvider.cs | 12 +- .../WinRT/Composition/WinUIEffectBase.cs | 27 ++- .../Composition/WinUiCompositedWindow.cs | 7 +- .../WinUiCompositedWindowSurface.cs | 49 +++-- .../Composition/WinUiCompositionShared.cs | 10 +- .../Composition/WinUiCompositionUtils.cs | 11 +- .../Composition/WinUiCompositorConnection.cs | 16 +- .../WinRT/NativeWinRTMethods.cs | 20 +- .../Avalonia.Win32/WinRT/WinRTInspectable.cs | 7 +- src/Windows/Avalonia.Win32/WinScreen.cs | 8 +- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 82 ++++--- .../WindowImpl.CustomCaptionProc.cs | 8 +- .../Avalonia.Win32/WindowImpl.WndProc.cs | 7 - src/Windows/Avalonia.Win32/WindowImpl.cs | 206 +++++++++++------- .../WindowsMountedVolumeInfoListener.cs | 13 +- src/tools/DevGenerators/Helpers.cs | 11 +- 76 files changed, 713 insertions(+), 752 deletions(-) diff --git a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs index bf18a7da5b..3dbc7c1bb2 100644 --- a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs @@ -26,6 +26,6 @@ namespace Avalonia.Platform bool CurrentThreadIsLoopThread { get; } - event Action Signaled; + event Action? Signaled; } } diff --git a/src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs b/src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs index 75076e2bb8..f9c7f9685d 100644 --- a/src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs +++ b/src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs @@ -7,9 +7,9 @@ namespace Avalonia.Platform.Storage; /// public sealed class FilePickerFileType { - public FilePickerFileType(string name) + public FilePickerFileType(string? name) { - Name = name; + Name = name ?? string.Empty; } /// diff --git a/src/Avalonia.Base/Rendering/IRenderTimer.cs b/src/Avalonia.Base/Rendering/IRenderTimer.cs index 07af7eeec8..14fcffd6a9 100644 --- a/src/Avalonia.Base/Rendering/IRenderTimer.cs +++ b/src/Avalonia.Base/Rendering/IRenderTimer.cs @@ -1,5 +1,4 @@ using System; -using System.Threading.Tasks; using Avalonia.Metadata; namespace Avalonia.Rendering diff --git a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs index 0658f9211c..d609dd94c8 100644 --- a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs +++ b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs @@ -13,6 +13,6 @@ namespace Avalonia.Platform /// /// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit. /// - event EventHandler ShutdownRequested; + event EventHandler? ShutdownRequested; } } diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 8d9d8e0e7b..31b144ce00 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -19,7 +19,7 @@ namespace Avalonia.Platform /// /// Gets or sets a method called when the minimized/maximized state of the window changes. /// - Action WindowStateChanged { get; set; } + Action? WindowStateChanged { get; set; } /// /// Sets the title of the window. @@ -42,7 +42,7 @@ namespace Avalonia.Platform /// /// Called when a disabled window received input. Can be used to activate child windows. /// - Action GotInputWhenDisabled { get; set; } + Action? GotInputWhenDisabled { get; set; } /// /// Enables or disables system window decorations (title bar, buttons, etc) @@ -68,7 +68,7 @@ namespace Avalonia.Platform /// Gets or sets a method called before the underlying implementation is destroyed. /// Return true to prevent the underlying implementation from closing. /// - Func Closing { get; set; } + Func? Closing { get; set; } /// /// Gets a value to indicate if the platform was able to extend client area to non-client area. @@ -78,7 +78,7 @@ namespace Avalonia.Platform /// /// Gets or Sets an action that is called whenever one of the extend client area properties changed. /// - Action ExtendClientAreaToDecorationsChanged { get; set; } + Action? ExtendClientAreaToDecorationsChanged { get; set; } /// /// Gets a flag that indicates if Managed decorations i.e. caption buttons are required. diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 0c9a91148b..26e11f0d4a 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -94,7 +94,7 @@ namespace Avalonia.Controls private set { SetAndRaise(IsActiveProperty, ref _isActive, value); } } - public Screens Screens { get; private set; } + public Screens Screens { get; } /// /// Gets or sets the owner of the window. diff --git a/src/Windows/Avalonia.Win32/AngleOptions.cs b/src/Windows/Avalonia.Win32/AngleOptions.cs index 076ddd2a5d..94c67120df 100644 --- a/src/Windows/Avalonia.Win32/AngleOptions.cs +++ b/src/Windows/Avalonia.Win32/AngleOptions.cs @@ -17,6 +17,6 @@ namespace Avalonia.Win32 new GlVersion(GlProfileType.OpenGLES, 2, 0) }; - public IList AllowedPlatformApis { get; set; } = null; + public IList? AllowedPlatformApis { get; set; } = null; } } diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs index 5f3f863493..aaad9cb3ba 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs @@ -2,8 +2,6 @@ using Avalonia.Automation.Provider; using UIA = Avalonia.Win32.Interop.Automation; -#nullable enable - namespace Avalonia.Win32.Automation { internal partial class AutomationNode : UIA.IExpandCollapseProvider diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.RangeValue.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.RangeValue.cs index b91cb76888..518c945aa9 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.RangeValue.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.RangeValue.cs @@ -1,8 +1,6 @@ using Avalonia.Automation.Provider; using UIA = Avalonia.Win32.Interop.Automation; -#nullable enable - namespace Avalonia.Win32.Automation { internal partial class AutomationNode : UIA.IRangeValueProvider diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Scroll.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Scroll.cs index 4f2d4ae269..0248a8d6a7 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Scroll.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Scroll.cs @@ -1,8 +1,6 @@ using Avalonia.Automation.Provider; using UIA = Avalonia.Win32.Interop.Automation; -#nullable enable - namespace Avalonia.Win32.Automation { internal partial class AutomationNode : UIA.IScrollProvider, UIA.IScrollItemProvider diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs index 61903ab5b0..3751bb6476 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs @@ -1,12 +1,9 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Avalonia.Automation.Peers; using Avalonia.Automation.Provider; using UIA = Avalonia.Win32.Interop.Automation; -#nullable enable - namespace Avalonia.Win32.Automation { internal partial class AutomationNode : UIA.ISelectionProvider, UIA.ISelectionItemProvider @@ -27,8 +24,7 @@ namespace Avalonia.Win32.Automation UIA.IRawElementProviderSimple[] UIA.ISelectionProvider.GetSelection() { var peers = InvokeSync>(x => x.GetSelection()); - return peers?.Select(x => (UIA.IRawElementProviderSimple)GetOrCreate(x)!).ToArray() ?? - Array.Empty(); + return peers.Select(x => (UIA.IRawElementProviderSimple)GetOrCreate(x)).ToArray(); } void UIA.ISelectionItemProvider.AddToSelection() => InvokeSync(x => x.AddToSelection()); diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs index 38f4d80946..08f4b62d83 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs @@ -1,8 +1,6 @@ using Avalonia.Automation.Provider; using UIA = Avalonia.Win32.Interop.Automation; -#nullable enable - namespace Avalonia.Win32.Automation { internal partial class AutomationNode : UIA.IToggleProvider diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs index 34f5dfe0b9..204e159049 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs @@ -2,8 +2,6 @@ using Avalonia.Automation.Provider; using UIA = Avalonia.Win32.Interop.Automation; -#nullable enable - namespace Avalonia.Win32.Automation { internal partial class AutomationNode : UIA.IValueProvider diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index 29ab2cea3a..5ca4ef63bf 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -12,8 +12,6 @@ using Avalonia.Threading; using Avalonia.Win32.Interop.Automation; using AAP = Avalonia.Automation.Provider; -#nullable enable - namespace Avalonia.Win32.Automation { [ComVisible(true)] @@ -25,7 +23,7 @@ namespace Avalonia.Win32.Automation IRawElementProviderAdviseEvents, IInvokeProvider { - private static Dictionary s_propertyMap = new Dictionary() + private static Dictionary s_propertyMap = new() { { AutomationElementIdentifiers.BoundingRectangleProperty, UiaPropertyId.BoundingRectangle }, { AutomationElementIdentifiers.ClassNameProperty, UiaPropertyId.ClassName }, @@ -46,8 +44,7 @@ namespace Avalonia.Win32.Automation { SelectionPatternIdentifiers.SelectionProperty, UiaPropertyId.SelectionSelection }, }; - private static ConditionalWeakTable s_nodes = - new ConditionalWeakTable(); + private static ConditionalWeakTable s_nodes = new(); private readonly int[] _runtimeId; private int _raiseFocusChanged; @@ -174,11 +171,12 @@ namespace Avalonia.Win32.Automation NavigateDirection.LastChild => GetOrCreate(Peer.GetChildren().LastOrDefault()), _ => null, }; - }) as IRawElementProviderFragment; + }); } public void SetFocus() => InvokeSync(() => Peer.SetFocus()); + [return: NotNullIfNotNull(nameof(peer))] public static AutomationNode? GetOrCreate(AutomationPeer? peer) { return peer is null ? null : s_nodes.GetValue(peer, Create); diff --git a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs index b732c4169f..3d8b4995ad 100644 --- a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs @@ -6,8 +6,6 @@ using Avalonia.Automation.Provider; using Avalonia.Platform; using Avalonia.Win32.Interop.Automation; -#nullable enable - namespace Avalonia.Win32.Automation { [RequiresUnreferencedCode("Requires .NET COM interop")] diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index a24fe31df8..754e883c2f 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -6,7 +6,6 @@ - @@ -16,7 +15,8 @@ - + + diff --git a/src/Windows/Avalonia.Win32/ClipboardFormats.cs b/src/Windows/Avalonia.Win32/ClipboardFormats.cs index 7bd7765f8c..5fc4f21b2e 100644 --- a/src/Windows/Avalonia.Win32/ClipboardFormats.cs +++ b/src/Windows/Avalonia.Win32/ClipboardFormats.cs @@ -8,15 +8,15 @@ using Avalonia.Utilities; namespace Avalonia.Win32 { - static class ClipboardFormats + internal static class ClipboardFormats { private const int MAX_FORMAT_NAME_LENGTH = 260; - class ClipboardFormat + private class ClipboardFormat { - public ushort Format { get; private set; } - public string Name { get; private set; } - public ushort[] Synthesized { get; private set; } + public ushort Format { get; } + public string Name { get; } + public ushort[] Synthesized { get; } public ClipboardFormat(string name, ushort format, params ushort[] synthesized) { @@ -26,14 +26,14 @@ namespace Avalonia.Win32 } } - private static readonly List FormatList = new List() + private static readonly List s_formatList = new() { new ClipboardFormat(DataFormats.Text, (ushort)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, (ushort)UnmanagedMethods.ClipboardFormat.CF_TEXT), new ClipboardFormat(DataFormats.FileNames, (ushort)UnmanagedMethods.ClipboardFormat.CF_HDROP), }; - private static string QueryFormatName(ushort format) + private static string? QueryFormatName(ushort format) { var sb = StringBuilderCache.Acquire(MAX_FORMAT_NAME_LENGTH); if (UnmanagedMethods.GetClipboardFormatName(format, sb, sb.Capacity) > 0) @@ -43,16 +43,16 @@ namespace Avalonia.Win32 public static string GetFormat(ushort format) { - lock (FormatList) + lock (s_formatList) { - var pd = FormatList.FirstOrDefault(f => f.Format == format || Array.IndexOf(f.Synthesized, format) >= 0); + var pd = s_formatList.FirstOrDefault(f => f.Format == format || Array.IndexOf(f.Synthesized, format) >= 0); if (pd == null) { - string name = QueryFormatName(format); + string? name = QueryFormatName(format); if (string.IsNullOrEmpty(name)) - name = string.Format("Unknown_Format_{0}", format); + name = $"Unknown_Format_{format}"; pd = new ClipboardFormat(name, format); - FormatList.Add(pd); + s_formatList.Add(pd); } return pd.Name; } @@ -60,16 +60,16 @@ namespace Avalonia.Win32 public static ushort GetFormat(string format) { - lock (FormatList) + lock (s_formatList) { - var pd = FormatList.FirstOrDefault(f => StringComparer.OrdinalIgnoreCase.Equals(f.Name, format)); + var pd = s_formatList.FirstOrDefault(f => StringComparer.OrdinalIgnoreCase.Equals(f.Name, format)); if (pd == null) { int id = UnmanagedMethods.RegisterClipboardFormat(format); if (id == 0) throw new Win32Exception(); pd = new ClipboardFormat(format, (ushort)id); - FormatList.Add(pd); + s_formatList.Add(pd); } return pd.Format; } diff --git a/src/Windows/Avalonia.Win32/ClipboardImpl.cs b/src/Windows/Avalonia.Win32/ClipboardImpl.cs index 82fd1109f4..1a760aeab8 100644 --- a/src/Windows/Avalonia.Win32/ClipboardImpl.cs +++ b/src/Windows/Avalonia.Win32/ClipboardImpl.cs @@ -30,7 +30,7 @@ namespace Avalonia.Win32 return Disposable.Create(() => UnmanagedMethods.CloseClipboard()); } - public async Task GetTextAsync() + public async Task GetTextAsync() { using(await OpenClipboard()) { @@ -52,19 +52,17 @@ namespace Avalonia.Win32 } } - public async Task SetTextAsync(string text) + public async Task SetTextAsync(string? text) { - if (text == null) - { - throw new ArgumentNullException(nameof(text)); - } - using(await OpenClipboard()) { UnmanagedMethods.EmptyClipboard(); - var hGlobal = Marshal.StringToHGlobalUni(text); - UnmanagedMethods.SetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, hGlobal); + if (text is not null) + { + var hGlobal = Marshal.StringToHGlobalUni(text); + UnmanagedMethods.SetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, hGlobal); + } } } @@ -121,7 +119,7 @@ namespace Avalonia.Win32 } } - public async Task GetDataAsync(string format) + public async Task GetDataAsync(string format) { Dispatcher.UIThread.VerifyAccess(); var i = OleRetryCount; diff --git a/src/Windows/Avalonia.Win32/DataObject.cs b/src/Windows/Avalonia.Win32/DataObject.cs index 7d22e57a30..272300cbf3 100644 --- a/src/Windows/Avalonia.Win32/DataObject.cs +++ b/src/Windows/Avalonia.Win32/DataObject.cs @@ -22,9 +22,9 @@ namespace Avalonia.Win32 // Compatibility with WinForms + WPF... internal static readonly byte[] SerializedObjectGUID = new Guid("FD9EA796-3B13-4370-A679-56106BB288FB").ToByteArray(); - class FormatEnumerator : CallbackBase, Win32Com.IEnumFORMATETC + private class FormatEnumerator : CallbackBase, Win32Com.IEnumFORMATETC { - private FORMATETC[] _formats; + private readonly FORMATETC[] _formats; private uint _current; private FormatEnumerator(FORMATETC[] formats, uint current) @@ -105,16 +105,12 @@ namespace Avalonia.Win32 public DataObject(IDataObject wrapped) { - if (wrapped == null) + _wrapped = wrapped switch { - throw new ArgumentNullException(nameof(wrapped)); - } - if (_wrapped is DataObject || _wrapped is OleDataObject) - { - throw new InvalidOperationException(); - } - - _wrapped = wrapped; + null => throw new ArgumentNullException(nameof(wrapped)), + DataObject or OleDataObject => throw new ArgumentException($"Cannot wrap a {wrapped.GetType()}"), + _ => wrapped + }; } #region IDataObject @@ -128,17 +124,17 @@ namespace Avalonia.Win32 return _wrapped.GetDataFormats(); } - IEnumerable IDataObject.GetFileNames() + IEnumerable? IDataObject.GetFileNames() { return _wrapped.GetFileNames(); } - string IDataObject.GetText() + string? IDataObject.GetText() { return _wrapped.GetText(); } - object IDataObject.Get(string dataFormat) + object? IDataObject.Get(string dataFormat) { return _wrapped.Get(dataFormat); } @@ -182,9 +178,6 @@ namespace Avalonia.Win32 if (_wrapped is Win32Com.IDataObject ole) return ole.GetCanonicalFormatEtc(formatIn); - var formatOut = new FORMATETC(); - formatOut.ptd = IntPtr.Zero; - throw new COMException(nameof(UnmanagedMethods.HRESULT.E_NOTIMPL), unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL)); } @@ -264,9 +257,9 @@ namespace Avalonia.Win32 private uint WriteDataToHGlobal(string dataFormat, ref IntPtr hGlobal) { - object data = _wrapped.Get(dataFormat); + object data = _wrapped.Get(dataFormat)!; if (dataFormat == DataFormats.Text || data is string) - return WriteStringToHGlobal(ref hGlobal, Convert.ToString(data)); + return WriteStringToHGlobal(ref hGlobal, Convert.ToString(data) ?? string.Empty); if (dataFormat == DataFormats.FileNames && data is IEnumerable files) return WriteFileListToHGlobal(ref hGlobal, files); if (data is Stream stream) @@ -286,7 +279,7 @@ namespace Avalonia.Win32 } if (data is IEnumerable bytes) { - var byteArr = bytes is byte[] ? (byte[])bytes : bytes.ToArray(); + var byteArr = bytes as byte[] ?? bytes.ToArray(); return WriteBytesToHGlobal(ref hGlobal, byteArr); } return WriteBytesToHGlobal(ref hGlobal, SerializeObject(data)); @@ -332,7 +325,7 @@ namespace Avalonia.Win32 private static uint WriteFileListToHGlobal(ref IntPtr hGlobal, IEnumerable files) { - if (!files?.Any() ?? false) + if (!files.Any()) return unchecked((int)UnmanagedMethods.HRESULT.S_OK); char[] filesStr = (string.Join("\0", files) + "\0\0").ToCharArray(); @@ -392,7 +385,7 @@ namespace Avalonia.Win32 public void ReleaseWrapped() { - _wrapped = null; + _wrapped = null!; } #endregion } diff --git a/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs b/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs index c11a6026e7..e59eb98c6c 100644 --- a/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs +++ b/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs @@ -1,17 +1,10 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; using static Avalonia.Win32.Interop.UnmanagedMethods; // ReSharper disable InconsistentNaming #pragma warning disable CS0649 namespace Avalonia.Win32.DirectX { -#nullable enable public unsafe struct HANDLE { public readonly void* Value; @@ -288,5 +281,4 @@ namespace Avalonia.Win32.DirectX public D3D11_RESOURCE_MISC_FLAG MiscFlags; } -#nullable restore } diff --git a/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs b/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs index e82f7633be..26696ef903 100644 --- a/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs +++ b/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs @@ -1,24 +1,14 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; using System.Threading.Tasks; using Avalonia.Logging; -using Avalonia.OpenGL.Angle; -using Avalonia.OpenGL.Egl; using Avalonia.Rendering; -using Avalonia.Win32.OpenGl.Angle; using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.DirectX.DirectXUnmanagedMethods; using MicroCom.Runtime; namespace Avalonia.Win32.DirectX { -#pragma warning disable CA1416 // This should only be reachable on Windows -#nullable enable public unsafe class DxgiConnection : IRenderTimer { public const uint ENUM_CURRENT_SETTINGS = unchecked((uint)(-1)); @@ -26,11 +16,11 @@ namespace Avalonia.Win32.DirectX public bool RunsInBackground => true; public event Action? Tick; - private object _syncLock; + private readonly object _syncLock; - private IDXGIOutput? _output = null; + private IDXGIOutput? _output; - private Stopwatch? _stopwatch = null; + private Stopwatch? _stopwatch; private const string LogArea = "DXGI"; public DxgiConnection(object syncLock) @@ -51,9 +41,9 @@ namespace Avalonia.Win32.DirectX } } - private unsafe void RunLoop() + private void RunLoop() { - _stopwatch = System.Diagnostics.Stopwatch.StartNew(); + _stopwatch = Stopwatch.StartNew(); try { GetBestOutputToVWaitOn(); @@ -162,7 +152,7 @@ namespace Avalonia.Win32.DirectX } // Used the windows composition as a blueprint for this startup/creation - static private bool TryCreateAndRegisterCore() + private static bool TryCreateAndRegisterCore() { var tcs = new TaskCompletionSource(); var pumpLock = new object(); @@ -170,9 +160,7 @@ namespace Avalonia.Win32.DirectX { try { - DxgiConnection connection; - - connection = new DxgiConnection(pumpLock); + var connection = new DxgiConnection(pumpLock); AvaloniaLocator.CurrentMutable.BindToSelf(connection); AvaloniaLocator.CurrentMutable.Bind().ToConstant(connection); @@ -191,6 +179,4 @@ namespace Avalonia.Win32.DirectX return tcs.Task.Result; } } -#nullable restore -#pragma warning restore CA1416 // Validate platform compatibility } diff --git a/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs b/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs index cb7826e185..065386b401 100644 --- a/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs +++ b/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs @@ -1,40 +1,28 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -using Avalonia.OpenGL.Angle; using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; using Avalonia.Win32.OpenGl.Angle; using MicroCom.Runtime; -using static Avalonia.OpenGL.Egl.EglGlPlatformSurfaceBase; using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32.DirectX { -#pragma warning disable CA1416 // Validate platform compatibility, if you enter this not on windows you have messed up badly -#nullable enable public unsafe class DxgiRenderTarget : EglPlatformSurfaceRenderTargetBase { // DXGI_FORMAT_B8G8R8A8_UNORM is target texture format as per ANGLE documentation public const uint DXGI_USAGE_RENDER_TARGET_OUTPUT = 0x00000020U; + private readonly Guid ID3D11Texture2DGuid = Guid.Parse("6F15AAF2-D208-4E89-9AB4-489535D34F9C"); - private EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _window; - private DxgiConnection _connection; - private IDXGIDevice? _dxgiDevice = null; - private IDXGIFactory2? _dxgiFactory = null; - private IDXGISwapChain1? _swapChain = null; - private IUnknown? _renderTexture = null; + private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _window; + private readonly DxgiConnection _connection; + private readonly IDXGIDevice? _dxgiDevice; + private readonly IDXGIFactory2? _dxgiFactory; + private readonly IDXGISwapChain1? _swapChain; + private readonly uint _flagsUsed; - private Interop.UnmanagedMethods.RECT _clientRect = default; - - private uint _flagsUsed; - - private Guid ID3D11Texture2DGuid = Guid.Parse("6F15AAF2-D208-4E89-9AB4-489535D34F9C"); + private IUnknown? _renderTexture; + private RECT _clientRect; public DxgiRenderTarget(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo window, EglContext context, DxgiConnection connection) : base(context) { @@ -80,11 +68,11 @@ namespace Avalonia.Win32.DirectX null ); - Interop.UnmanagedMethods.RECT pClientRect; - GetClientRect(_window.Handle, out pClientRect); + GetClientRect(_window.Handle, out var pClientRect); _clientRect = pClientRect; } + /// public override IGlPlatformSurfaceRenderingSession BeginDrawCore() { if (_swapChain is null) @@ -98,8 +86,7 @@ namespace Avalonia.Win32.DirectX var success = false; try { - Interop.UnmanagedMethods.RECT pClientRect; - GetClientRect(_window.Handle, out pClientRect); + GetClientRect(_window.Handle, out var pClientRect); if (!RectsEqual(pClientRect, _clientRect)) { // we gotta resize @@ -137,7 +124,7 @@ namespace Avalonia.Win32.DirectX var res = base.BeginDraw(surface, _window.Size, _window.Scaling, () => { _swapChain.Present((ushort)0U, (ushort)0U); - surface?.Dispose(); + surface.Dispose(); transaction?.Dispose(); contextLock?.Dispose(); }, true); @@ -178,6 +165,4 @@ namespace Avalonia.Win32.DirectX } } -#pragma warning restore CA1416 // Validate platform compatibility -#nullable restore } diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs index 8feecab4dd..cba4879f5b 100644 --- a/src/Windows/Avalonia.Win32/FramebufferManager.cs +++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs @@ -4,8 +4,6 @@ using Avalonia.Controls.Platform.Surfaces; using Avalonia.Platform; using Avalonia.Win32.Interop; -#nullable enable - namespace Avalonia.Win32 { internal class FramebufferManager : IFramebufferPlatformSurface, IDisposable diff --git a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs index 7ef1cb1d1c..41417dd950 100644 --- a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs +++ b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Text; using Avalonia.Input.TextInput; using Avalonia.Threading; @@ -10,21 +11,21 @@ namespace Avalonia.Win32.Input /// /// A Windows input method editor based on Windows Input Method Manager (IMM32). /// - class Imm32InputMethod : ITextInputMethodImpl + internal class Imm32InputMethod : ITextInputMethodImpl { - public IntPtr HWND { get; private set; } + public IntPtr Hwnd { get; private set; } private IntPtr _currentHimc; - private WindowImpl _parent; - private ITextInputMethodClient _client; + private WindowImpl? _parent; - private Imm32CaretManager _caretManager = new(); + private Imm32CaretManager _caretManager; private ushort _langId; - private const int _caretMargin = 1; + private const int CaretMargin = 1; - public ITextInputMethodClient Client => _client; + public ITextInputMethodClient? Client { get; private set; } - public bool IsActive => _client != null; + [MemberNotNullWhen(true, nameof(Client))] + public bool IsActive => Client != null; public bool IsComposing { get; set; } @@ -32,12 +33,12 @@ namespace Avalonia.Win32.Input public void CreateCaret() { - _caretManager.TryCreate(HWND); + _caretManager.TryCreate(Hwnd); } public void EnableImm() { - var himc = ImmGetContext(HWND); + var himc = ImmGetContext(Hwnd); if(himc == IntPtr.Zero) { @@ -51,13 +52,13 @@ namespace Avalonia.Win32.Input DisableImm(); } - ImmAssociateContext(HWND, himc); + ImmAssociateContext(Hwnd, himc); - ImmReleaseContext(HWND, himc); + ImmReleaseContext(Hwnd, himc); _currentHimc = himc; - _caretManager.TryCreate(HWND); + _caretManager.TryCreate(Hwnd); } } @@ -67,7 +68,7 @@ namespace Avalonia.Win32.Input Reset(); - ImmAssociateContext(HWND, IntPtr.Zero); + ImmAssociateContext(Hwnd, IntPtr.Zero); _caretManager.TryDestroy(); @@ -76,7 +77,7 @@ namespace Avalonia.Win32.Input public void SetLanguageAndWindow(WindowImpl parent, IntPtr hwnd, IntPtr HKL) { - HWND = hwnd; + Hwnd = hwnd; _parent = parent; _langId = PRIMARYLANGID(LGID(HKL)); @@ -98,9 +99,9 @@ namespace Avalonia.Win32.Input { DisableImm(); - HWND = IntPtr.Zero; + Hwnd = IntPtr.Zero; _parent = null; - _client = null; + Client = null; _langId = 0; IsComposing = false; @@ -108,13 +109,13 @@ namespace Avalonia.Win32.Input //Dependant on CurrentThread. When Avalonia will support Multiple Dispatchers - //every Dispatcher should have their own InputMethod. - public static Imm32InputMethod Current { get; } = new Imm32InputMethod(); + public static Imm32InputMethod Current { get; } = new(); public void Reset() { Dispatcher.UIThread.Post(() => { - var himc = ImmGetContext(HWND); + var himc = ImmGetContext(Hwnd); if (IsComposing) { @@ -123,13 +124,13 @@ namespace Avalonia.Win32.Input IsComposing = false; } - ImmReleaseContext(HWND, himc); + ImmReleaseContext(Hwnd, himc); }); } - public void SetClient(ITextInputMethodClient client) + public void SetClient(ITextInputMethodClient? client) { - _client = client; + Client = client; Dispatcher.UIThread.Post(() => { @@ -152,7 +153,7 @@ namespace Avalonia.Win32.Input public void SetCursorRect(Rect rect) { - var focused = GetActiveWindow() == HWND; + var focused = GetActiveWindow() == Hwnd; if (!focused) { @@ -161,7 +162,7 @@ namespace Avalonia.Win32.Input Dispatcher.UIThread.Post(() => { - var himc = ImmGetContext(HWND); + var himc = ImmGetContext(Hwnd); if (himc == IntPtr.Zero) { @@ -170,7 +171,7 @@ namespace Avalonia.Win32.Input MoveImeWindow(rect, himc); - ImmReleaseContext(HWND, himc); + ImmReleaseContext(Hwnd, himc); }); } @@ -219,7 +220,7 @@ namespace Avalonia.Win32.Input // the caret to move the position of their candidate windows. // On the other hand, Korean IMEs require the lower-left corner of the // caret to move their candidate windows. - y2 += _caretMargin; + y2 += CaretMargin; } // Need to return here since some Chinese IMEs would stuck if set @@ -238,7 +239,7 @@ namespace Avalonia.Win32.Input dwIndex = 0, dwStyle = CFS_EXCLUDE, ptCurrentPos = new POINT {X = x1, Y = y1}, - rcArea = new RECT {left = x1, top = y1, right = x2, bottom = y2 + _caretMargin} + rcArea = new RECT {left = x1, top = y1, right = x2, bottom = y2 + CaretMargin} }; ImmSetCandidateWindow(himc, ref excludeRectangle); @@ -275,34 +276,21 @@ namespace Avalonia.Win32.Input return; } - if(!IsActive || !_client.SupportsPreedit) + if(!IsActive || !Client.SupportsPreedit) { return; } var composition = GetCompositionString(); - _client.SetPreeditText(composition); + Client.SetPreeditText(composition); } - private string GetCompositionString() + private string? GetCompositionString() { - var himc = ImmGetContext(HWND); + var himc = ImmGetContext(Hwnd); - var length = ImmGetCompositionString(himc, GCS.GCS_COMPSTR, IntPtr.Zero, 0); - - var buffer = new byte[length]; - - unsafe - { - fixed (byte* bufferPtr = buffer) - { - var error = ImmGetCompositionString(himc, GCS.GCS_COMPSTR, (IntPtr)bufferPtr, (uint)length); - - return Encoding.Unicode.GetString(buffer, 0, buffer.Length); - } - } - + return ImmGetCompositionString(himc, GCS.GCS_COMPSTR); } ~Imm32InputMethod() diff --git a/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs b/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs index 998ff4a427..c7bc511b99 100644 --- a/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs +++ b/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs @@ -5,9 +5,10 @@ using Avalonia.Win32.Interop; namespace Avalonia.Win32.Input { - class WindowsMouseDevice : MouseDevice + internal class WindowsMouseDevice : MouseDevice { private readonly IPointer _pointer; + public WindowsMouseDevice() : base(WindowsMousePointer.CreatePointer(out var pointer)) { _pointer = pointer; @@ -15,14 +16,14 @@ namespace Avalonia.Win32.Input // Normally user should use IPointer.Capture instead of MouseDevice.Capture, // But on Windows we need to handle WM_MOUSE capture manually without having access to the Pointer. - internal void Capture(IInputElement control) + internal void Capture(IInputElement? control) { _pointer.Capture(control); } internal class WindowsMousePointer : Pointer { - private WindowsMousePointer() : base(Pointer.GetNextFreeId(),PointerType.Mouse, true) + private WindowsMousePointer() : base(GetNextFreeId(),PointerType.Mouse, true) { } @@ -31,7 +32,7 @@ namespace Avalonia.Win32.Input return pointer = new WindowsMousePointer(); } - protected override void PlatformCapture(IInputElement element) + protected override void PlatformCapture(IInputElement? element) { var hwnd = (TopLevel.GetTopLevel(element as Visual)?.PlatformImpl as WindowImpl) ?.Handle.Handle; diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IGridProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IGridProvider.cs index a8caf26524..be8b998a9e 100644 --- a/src/Windows/Avalonia.Win32/Interop/Automation/IGridProvider.cs +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IGridProvider.cs @@ -1,4 +1,3 @@ -using System; using System.Runtime.InteropServices; namespace Avalonia.Win32.Interop.Automation @@ -8,7 +7,7 @@ namespace Avalonia.Win32.Interop.Automation [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IGridProvider { - IRawElementProviderSimple GetItem(int row, int column); + IRawElementProviderSimple? GetItem(int row, int column); int RowCount { get; } int ColumnCount { get; } } diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragment.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragment.cs index a62aa842cb..46d8dc6cdb 100644 --- a/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragment.cs +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragment.cs @@ -1,8 +1,6 @@ using System; using System.Runtime.InteropServices; -#nullable enable - namespace Avalonia.Win32.Interop.Automation { [ComVisible(true)] diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragmentRoot.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragmentRoot.cs index 71d1bdce60..1fd0cb13ba 100644 --- a/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragmentRoot.cs +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragmentRoot.cs @@ -1,4 +1,3 @@ -using System; using System.Runtime.InteropServices; namespace Avalonia.Win32.Interop.Automation @@ -8,7 +7,7 @@ namespace Avalonia.Win32.Interop.Automation [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IRawElementProviderFragmentRoot : IRawElementProviderFragment { - IRawElementProviderFragment ElementProviderFromPoint(double x, double y); - IRawElementProviderFragment GetFocus(); + IRawElementProviderFragment? ElementProviderFromPoint(double x, double y); + IRawElementProviderFragment? GetFocus(); } } diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple.cs index 439036290e..5b67d10206 100644 --- a/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple.cs +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple.cs @@ -1,8 +1,6 @@ using System; using System.Runtime.InteropServices; -#nullable enable - namespace Avalonia.Win32.Interop.Automation { [Flags] diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple2.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple2.cs index f3504b8d77..c2e8a42f87 100644 --- a/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple2.cs +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple2.cs @@ -1,7 +1,4 @@ -using System; -using System.Runtime.InteropServices; - -#nullable enable +using System.Runtime.InteropServices; namespace Avalonia.Win32.Interop.Automation { diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/ISelectionItemProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/ISelectionItemProvider.cs index 8d6677315c..072e210027 100644 --- a/src/Windows/Avalonia.Win32/Interop/Automation/ISelectionItemProvider.cs +++ b/src/Windows/Avalonia.Win32/Interop/Automation/ISelectionItemProvider.cs @@ -1,5 +1,3 @@ -#nullable enable -using System; using System.Runtime.InteropServices; namespace Avalonia.Win32.Interop.Automation diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IValueProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IValueProvider.cs index 919be647f8..0356f93ab8 100644 --- a/src/Windows/Avalonia.Win32/Interop/Automation/IValueProvider.cs +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IValueProvider.cs @@ -1,8 +1,5 @@ -using System; using System.Runtime.InteropServices; -#nullable enable - namespace Avalonia.Win32.Interop.Automation { [ComVisible(true)] diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreProviderApi.cs b/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreProviderApi.cs index 4ba7a710d4..bd939c56b7 100644 --- a/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreProviderApi.cs +++ b/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreProviderApi.cs @@ -71,21 +71,21 @@ namespace Avalonia.Win32.Interop.Automation public static extern bool UiaClientsAreListening(); [DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)] - public static extern IntPtr UiaReturnRawElementProvider(IntPtr hwnd, IntPtr wParam, IntPtr lParam, IRawElementProviderSimple el); + public static extern IntPtr UiaReturnRawElementProvider(IntPtr hwnd, IntPtr wParam, IntPtr lParam, IRawElementProviderSimple? el); [DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)] public static extern int UiaHostProviderFromHwnd(IntPtr hwnd, [MarshalAs(UnmanagedType.Interface)] out IRawElementProviderSimple provider); [DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)] - public static extern int UiaRaiseAutomationEvent(IRawElementProviderSimple provider, int id); + public static extern int UiaRaiseAutomationEvent(IRawElementProviderSimple? provider, int id); [DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)] - public static extern int UiaRaiseAutomationPropertyChangedEvent(IRawElementProviderSimple provider, int id, object oldValue, object newValue); + public static extern int UiaRaiseAutomationPropertyChangedEvent(IRawElementProviderSimple? provider, int id, object? oldValue, object? newValue); [DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)] - public static extern int UiaRaiseStructureChangedEvent(IRawElementProviderSimple provider, StructureChangeType structureChangeType, int[] runtimeId, int runtimeIdLen); + public static extern int UiaRaiseStructureChangedEvent(IRawElementProviderSimple? provider, StructureChangeType structureChangeType, int[]? runtimeId, int runtimeIdLen); [DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)] - public static extern int UiaDisconnectProvider(IRawElementProviderSimple provider); + public static extern int UiaDisconnectProvider(IRawElementProviderSimple? provider); } } diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs b/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs index 08b3ee32fa..b6b069ac37 100644 --- a/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs +++ b/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs @@ -63,8 +63,8 @@ namespace Avalonia.Win32.Interop.Automation } #endif - var comConfig = AppContext.GetData("System.Runtime.InteropServices.BuiltInComInterop.IsSupported"); - return comConfig == null || bool.Parse(comConfig.ToString()); + var comConfig = AppContext.GetData("System.Runtime.InteropServices.BuiltInComInterop.IsSupported") as string; + return comConfig == null || bool.Parse(comConfig); } [DllImport("UIAutomationCore.dll", EntryPoint = "UiaLookupId", CharSet = CharSet.Unicode)] diff --git a/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs b/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs index 88b907aeec..7eb457480b 100644 --- a/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs +++ b/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs @@ -7,8 +7,8 @@ namespace Avalonia.Win32.Interop internal class TaskBarList { private static IntPtr s_taskBarList; - private static HrInit s_hrInitDelegate; - private static MarkFullscreenWindow s_markFullscreenWindowDelegate; + private static HrInit? s_hrInitDelegate; + private static MarkFullscreenWindow? s_markFullscreenWindowDelegate; /// /// Ported from https://github.com/chromium/chromium/blob/master/ui/views/win/fullscreen_handler.cc @@ -28,10 +28,7 @@ namespace Avalonia.Win32.Interop { var ptr = (ITaskBarList2VTable**)s_taskBarList.ToPointer(); - if (s_hrInitDelegate is null) - { - s_hrInitDelegate = Marshal.GetDelegateForFunctionPointer((*ptr)->HrInit); - } + s_hrInitDelegate ??= Marshal.GetDelegateForFunctionPointer((*ptr)->HrInit); if (s_hrInitDelegate(s_taskBarList) != HRESULT.S_OK) { @@ -44,10 +41,8 @@ namespace Avalonia.Win32.Interop { var ptr = (ITaskBarList2VTable**)s_taskBarList.ToPointer(); - if (s_markFullscreenWindowDelegate is null) - { - s_markFullscreenWindowDelegate = Marshal.GetDelegateForFunctionPointer((*ptr)->MarkFullscreenWindow); - } + s_markFullscreenWindowDelegate ??= + Marshal.GetDelegateForFunctionPointer((*ptr)->MarkFullscreenWindow); s_markFullscreenWindowDelegate(s_taskBarList, hwnd, fullscreen); } diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 672f3a781a..1dd2ef20c7 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1,14 +1,12 @@ +#nullable enable + using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; - -using Avalonia.MicroCom; using MicroCom.Runtime; -using Avalonia.Win32.Win32Com; // ReSharper disable InconsistentNaming #pragma warning disable 169, 649 @@ -1092,7 +1090,7 @@ namespace Avalonia.Win32.Interop public const int SizeOf_BITMAPINFOHEADER = 40; [DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] - unsafe internal static extern int GetMouseMovePointsEx( + internal static extern int GetMouseMovePointsEx( uint cbSize, MOUSEMOVEPOINT* pointsIn, MOUSEMOVEPOINT* pointsBufferOut, int nBufPoints, uint resolution); @@ -1167,7 +1165,7 @@ namespace Avalonia.Win32.Interop public static extern IntPtr CreateWindowEx( int dwExStyle, uint lpClassName, - string lpWindowName, + string? lpWindowName, uint dwStyle, int x, int y, @@ -1218,7 +1216,7 @@ namespace Avalonia.Win32.Interop public static extern int GetMessageTime(); [DllImport("kernel32.dll")] - public static extern IntPtr GetModuleHandle(string lpModuleName); + public static extern IntPtr GetModuleHandle(string? lpModuleName); [DllImport("user32.dll")] public static extern int GetSystemMetrics(SystemMetric smIndex); @@ -1255,7 +1253,7 @@ namespace Avalonia.Win32.Interop } else { - return (uint)SetWindowLong64b(hWnd, nIndex, new IntPtr((uint)value)).ToInt32(); + return (uint)SetWindowLong64b(hWnd, nIndex, new IntPtr(value)).ToInt32(); } } @@ -1421,7 +1419,7 @@ namespace Avalonia.Win32.Interop public static extern bool UnregisterClass(string lpClassName, IntPtr hInstance); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetWindowTextW")] - public static extern bool SetWindowText(IntPtr hwnd, string lpString); + public static extern bool SetWindowText(IntPtr hwnd, string? lpString); public enum ClassLongIndex : int { @@ -1482,7 +1480,7 @@ namespace Avalonia.Win32.Interop internal static extern int CoCreateInstance(ref Guid clsid, IntPtr ignore1, int ignore2, ref Guid iid, [Out] out IntPtr pUnkOuter); - internal unsafe static T CreateInstance(ref Guid clsid, ref Guid iid) where T : IUnknown + internal static T CreateInstance(ref Guid clsid, ref Guid iid) where T : IUnknown { var hresult = CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out IntPtr pUnk); if (hresult != 0) @@ -1490,7 +1488,7 @@ namespace Avalonia.Win32.Interop throw new COMException("CreateInstance", hresult); } using var unk = MicroComRuntime.CreateProxyFor(pUnk, true); - return MicroComRuntime.QueryInterface(unk); + return unk.QueryInterface(); } [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] @@ -1581,7 +1579,7 @@ namespace Avalonia.Win32.Interop public static extern bool GetMonitorInfo([In] IntPtr hMonitor, ref MONITORINFO lpmi); [DllImport("user32")] - public static extern unsafe bool GetTouchInputInfo( + public static extern bool GetTouchInputInfo( IntPtr hTouchInput, uint cInputs, TOUCHINPUT* pInputs, @@ -1680,7 +1678,7 @@ namespace Avalonia.Win32.Interop public static extern IntPtr GlobalSize(IntPtr hGlobal); [DllImport("shell32.dll", BestFitMapping = false, CharSet = CharSet.Auto)] - public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch); + public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder? lpszFile, int cch); [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)] internal static extern void DoDragDrop(IntPtr dataObject, IntPtr dropSource, int allowedEffects, [Out] out int finalEffect); @@ -1734,7 +1732,7 @@ namespace Avalonia.Win32.Interop public DWM_BLURBEHIND(bool enabled) { - fEnable = enabled ? true : false; + fEnable = enabled; hRgnBlur = IntPtr.Zero; fTransitionOnMaximized = false; dwFlags = DWM_BB.Enable; @@ -1855,20 +1853,22 @@ namespace Avalonia.Win32.Interop [DllImport("imm32.dll", SetLastError = false, CharSet = CharSet.Unicode)] public static extern int ImmGetCompositionString(IntPtr hIMC, GCS dwIndex, [Out, Optional] IntPtr lpBuf, uint dwBufLen); - public static string ImmGetCompositionString(IntPtr hIMC, GCS dwIndex) + public static string? ImmGetCompositionString(IntPtr hIMC, GCS dwIndex) { int bufferLength = ImmGetCompositionString(hIMC, dwIndex, IntPtr.Zero, 0); if (bufferLength > 0) { - var buffer = new byte[bufferLength]; + var buffer = bufferLength <= 64 ? stackalloc byte[bufferLength] : new byte[bufferLength]; - fixed(byte* bufferPtr = buffer) + fixed (byte* bufferPtr = buffer) { - var error = ImmGetCompositionString(hIMC, dwIndex, (IntPtr)bufferPtr, (uint)bufferLength); - - return Marshal.PtrToStringUni((IntPtr)bufferPtr); - } + var result = ImmGetCompositionString(hIMC, dwIndex, (IntPtr)bufferPtr, (uint)bufferLength); + if (result >= 0) + { + return Marshal.PtrToStringUni((IntPtr)bufferPtr); + } + } } return null; @@ -2380,7 +2380,7 @@ namespace Avalonia.Win32.Interop } } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct _DROPFILES { public Int32 pFiles; @@ -2390,7 +2390,7 @@ namespace Avalonia.Win32.Interop public bool fWide; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct STGMEDIUM { public TYMED tymed; @@ -2398,7 +2398,7 @@ namespace Avalonia.Win32.Interop public IntPtr pUnkForRelease; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct FORMATETC { public ushort cfFormat; @@ -2507,14 +2507,14 @@ namespace Avalonia.Win32.Interop public int uCallbackMessage; public IntPtr hIcon; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] - public string szTip; + public string? szTip; public int dwState = 0; public int dwStateMask = 0; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] - public string szInfo; + public string? szInfo; public int uTimeoutOrVersion; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] - public string szInfoTitle; + public string? szInfoTitle; public NIIF dwInfoFlags; } } diff --git a/src/Windows/Avalonia.Win32/NonPumpingSyncContext.cs b/src/Windows/Avalonia.Win32/NonPumpingSyncContext.cs index f295dd7394..0c02f643b2 100644 --- a/src/Windows/Avalonia.Win32/NonPumpingSyncContext.cs +++ b/src/Windows/Avalonia.Win32/NonPumpingSyncContext.cs @@ -1,7 +1,6 @@ using System; using System.Runtime.ConstrainedExecution; using System.Threading; -using Avalonia.Threading; using Avalonia.Utilities; using Avalonia.Win32.Interop; @@ -18,8 +17,8 @@ namespace Avalonia.Win32 SetSynchronizationContext(this); } - public override void Post(SendOrPostCallback d, object state) => _inner.Post(d, state); - public override void Send(SendOrPostCallback d, object state) => _inner.Send(d, state); + public override void Post(SendOrPostCallback d, object? state) => _inner.Post(d, state); + public override void Send(SendOrPostCallback d, object? state) => _inner.Send(d, state); #if !NET6_0_OR_GREATER [PrePrepareMethod] @@ -32,23 +31,15 @@ namespace Avalonia.Win32 public void Dispose() => SetSynchronizationContext(_inner); - public static IDisposable Use() + public static IDisposable? Use() { var current = Current; - if (current == null) - { - if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) - return null; - } - if (current is NonPumpingSyncContext) - return null; - - return new NonPumpingSyncContext(current); + return current is null or NonPumpingSyncContext ? null : new NonPumpingSyncContext(current); } internal class HelperImpl : NonPumpingLockHelper.IHelperImpl { - IDisposable NonPumpingLockHelper.IHelperImpl.Use() => NonPumpingSyncContext.Use(); + IDisposable? NonPumpingLockHelper.IHelperImpl.Use() => NonPumpingSyncContext.Use(); } } } diff --git a/src/Windows/Avalonia.Win32/OffscreenParentWindow.cs b/src/Windows/Avalonia.Win32/OffscreenParentWindow.cs index 9de105a3d5..7253b3c0ed 100644 --- a/src/Windows/Avalonia.Win32/OffscreenParentWindow.cs +++ b/src/Windows/Avalonia.Win32/OffscreenParentWindow.cs @@ -1,17 +1,18 @@ using System; using System.ComponentModel; using System.Runtime.InteropServices; -using Avalonia.Platform; using Avalonia.Win32.Interop; namespace Avalonia.Win32 { - class OffscreenParentWindow + internal class OffscreenParentWindow { public static IntPtr Handle { get; } = CreateParentWindow(); - private static UnmanagedMethods.WndProc s_wndProcDelegate; + + private static UnmanagedMethods.WndProc? s_wndProcDelegate; + private static IntPtr CreateParentWindow() { - s_wndProcDelegate = new UnmanagedMethods.WndProc(ParentWndProc); + s_wndProcDelegate = ParentWndProc; var wndClassEx = new UnmanagedMethods.WNDCLASSEX { diff --git a/src/Windows/Avalonia.Win32/OleContext.cs b/src/Windows/Avalonia.Win32/OleContext.cs index e41423a334..897033e835 100644 --- a/src/Windows/Avalonia.Win32/OleContext.cs +++ b/src/Windows/Avalonia.Win32/OleContext.cs @@ -11,18 +11,16 @@ namespace Avalonia.Win32 { internal class OleContext { - private static OleContext s_current; + private static OleContext? s_current; - internal static OleContext Current + internal static OleContext? Current { get { if (!IsValidOleThread()) return null; - if (s_current == null) - s_current = new OleContext(); - return s_current; + return s_current ??= new OleContext(); } } @@ -41,7 +39,7 @@ namespace Avalonia.Win32 Thread.CurrentThread.GetApartmentState() == ApartmentState.STA; } - internal bool RegisterDragDrop(IPlatformHandle hwnd, IDropTarget target) + internal bool RegisterDragDrop(IPlatformHandle? hwnd, IDropTarget? target) { if (hwnd?.HandleDescriptor != "HWND" || target == null) { @@ -52,7 +50,7 @@ namespace Avalonia.Win32 return UnmanagedMethods.RegisterDragDrop(hwnd.Handle, trgPtr) == UnmanagedMethods.HRESULT.S_OK; } - internal bool UnregisterDragDrop(IPlatformHandle hwnd) + internal bool UnregisterDragDrop(IPlatformHandle? hwnd) { if (hwnd?.HandleDescriptor != "HWND") { diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index 79b55e4f77..247d0340c3 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -8,7 +8,6 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Runtime.Serialization.Formatters.Binary; using Avalonia.Input; -using Avalonia.MicroCom; using Avalonia.Utilities; using Avalonia.Win32.Interop; using MicroCom.Runtime; @@ -35,23 +34,23 @@ namespace Avalonia.Win32 return GetDataFormatsCore().Distinct(); } - public string GetText() + public string? GetText() { - return (string)GetDataFromOleHGLOBAL(DataFormats.Text, DVASPECT.DVASPECT_CONTENT); + return (string?)GetDataFromOleHGLOBAL(DataFormats.Text, DVASPECT.DVASPECT_CONTENT); } - public IEnumerable GetFileNames() + public IEnumerable? GetFileNames() { - return (IEnumerable)GetDataFromOleHGLOBAL(DataFormats.FileNames, DVASPECT.DVASPECT_CONTENT); + return (IEnumerable?)GetDataFromOleHGLOBAL(DataFormats.FileNames, DVASPECT.DVASPECT_CONTENT); } - public object Get(string dataFormat) + public object? Get(string dataFormat) { return GetDataFromOleHGLOBAL(dataFormat, DVASPECT.DVASPECT_CONTENT); } [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "We still use BinaryFormatter for WinForms dragndrop compatability")] - private unsafe object GetDataFromOleHGLOBAL(string format, DVASPECT aspect) + private unsafe object? GetDataFromOleHGLOBAL(string format, DVASPECT aspect) { var formatEtc = new Interop.FORMATETC(); formatEtc.cfFormat = ClipboardFormats.GetFormat(format); @@ -100,7 +99,7 @@ namespace Avalonia.Win32 private static IEnumerable ReadFileNamesFromHGlobal(IntPtr hGlobal) { - List files = new List(); + var files = new List(); int fileCount = UnmanagedMethods.DragQueryFile(hGlobal, -1, null, 0); if (fileCount > 0) { @@ -118,7 +117,7 @@ namespace Avalonia.Win32 return files; } - private static string ReadStringFromHGlobal(IntPtr hGlobal) + private static string? ReadStringFromHGlobal(IntPtr hGlobal) { IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal); try diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 8bbdfe5fd9..a81652ffc2 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -1,5 +1,5 @@ using System; - +using System.Diagnostics.CodeAnalysis; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.MicroCom; @@ -13,16 +13,16 @@ namespace Avalonia.Win32 internal class OleDropTarget : CallbackBase, Win32Com.IDropTarget { private readonly IInputRoot _target; - private readonly ITopLevelImpl _tl; + private readonly ITopLevelImpl _topLevel; private readonly IDragDropDevice _dragDevice; - private IDataObject _currentDrag = null; + private IDataObject? _currentDrag; - public OleDropTarget(ITopLevelImpl tl, IInputRoot target) + public OleDropTarget(ITopLevelImpl topLevel, IInputRoot target, IDragDropDevice dragDevice) { - _dragDevice = AvaloniaLocator.Current.GetService(); - _tl = tl; + _topLevel = topLevel; _target = target; + _dragDevice = dragDevice; } public static DropEffect ConvertDropEffect(DragDropEffects operation) @@ -71,10 +71,11 @@ namespace Avalonia.Win32 unsafe void Win32Com.IDropTarget.DragEnter(Win32Com.IDataObject pDataObj, int grfKeyState, UnmanagedMethods.POINT pt, DropEffect* pdwEffect) { - var dispatch = _tl?.Input; + var dispatch = _topLevel.Input; if (dispatch == null) { *pdwEffect= (int)DropEffect.None; + return; } SetDataObject(pDataObj); @@ -94,10 +95,11 @@ namespace Avalonia.Win32 unsafe void Win32Com.IDropTarget.DragOver(int grfKeyState, UnmanagedMethods.POINT pt, DropEffect* pdwEffect) { - var dispatch = _tl?.Input; - if (dispatch == null) + var dispatch = _topLevel.Input; + if (dispatch == null || _currentDrag is null) { *pdwEffect = (int)DropEffect.None; + return; } var args = new RawDragEvent( @@ -115,14 +117,20 @@ namespace Avalonia.Win32 void Win32Com.IDropTarget.DragLeave() { + var dispatch = _topLevel.Input; + if (dispatch == null || _currentDrag is null) + { + return; + } + try { - _tl?.Input(new RawDragEvent( + dispatch(new RawDragEvent( _dragDevice, RawDragEventType.DragLeave, _target, default, - null, + _currentDrag, DragDropEffects.None, RawInputModifiers.None )); @@ -137,10 +145,11 @@ namespace Avalonia.Win32 { try { - var dispatch = _tl?.Input; + var dispatch = _topLevel.Input; if (dispatch == null) { *pdwEffect = (int)DropEffect.None; + return; } SetDataObject(pDataObj); @@ -163,6 +172,7 @@ namespace Avalonia.Win32 } } + [MemberNotNull(nameof(_currentDrag))] private void SetDataObject(Win32Com.IDataObject pDataObj) { var newDrag = GetAvaloniaObjectFromCOM(pDataObj); @@ -178,9 +188,9 @@ namespace Avalonia.Win32 // OleDataObject keeps COM reference, so it should be disposed. if (_currentDrag is OleDataObject oleDragSource) { - oleDragSource?.Dispose(); - _currentDrag = null; + oleDragSource.Dispose(); } + _currentDrag = null; } private Point GetDragLocation(UnmanagedMethods.POINT dragPoint) @@ -194,7 +204,7 @@ namespace Avalonia.Win32 ReleaseDataObject(); } - public static unsafe IDataObject GetAvaloniaObjectFromCOM(Win32Com.IDataObject pDataObj) + public static IDataObject GetAvaloniaObjectFromCOM(Win32Com.IDataObject pDataObj) { if (pDataObj is null) { @@ -205,8 +215,7 @@ namespace Avalonia.Win32 return disposableDataObject; } - var dataObject = MicroComRuntime.TryUnwrapManagedObject(pDataObj) as DataObject; - if (dataObject is not null) + if (MicroComRuntime.TryUnwrapManagedObject(pDataObj) is DataObject dataObject) { return dataObject; } diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleD3DTextureFeature.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleD3DTextureFeature.cs index 6c951010c8..ffdd4fdfd6 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleD3DTextureFeature.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleD3DTextureFeature.cs @@ -14,7 +14,7 @@ internal class AngleD3DTextureFeature : IGlPlatformSurfaceRenderTargetFactory Display: AngleWin32EglDisplay { PlatformApi: AngleOptions.PlatformApi.DirectX11 } } && surface is IDirect3D11TexturePlatformSurface; - class RenderTargetWrapper : EglPlatformSurfaceRenderTargetBase + private class RenderTargetWrapper : EglPlatformSurfaceRenderTargetBase { private readonly AngleWin32EglDisplay _angle; private readonly IDirect3D11TextureRenderTarget _target; @@ -31,8 +31,8 @@ internal class AngleD3DTextureFeature : IGlPlatformSurfaceRenderTargetFactory { var success = false; var contextLock = Context.EnsureCurrent(); - IDirect3D11TextureRenderTargetRenderSession session = null; - EglSurface surface = null; + IDirect3D11TextureRenderTargetRenderSession? session = null; + EglSurface? surface = null; try { try diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleEglInterface.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleEglInterface.cs index 4e00bc2c72..2fd2e0f33c 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleEglInterface.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleEglInterface.cs @@ -22,7 +22,7 @@ namespace Avalonia.OpenGL.Angle } [GetProcAddress("eglCreateDeviceANGLE", true)] - public partial IntPtr CreateDeviceANGLE(int deviceType, IntPtr nativeDevice, int[] attribs); + public partial IntPtr CreateDeviceANGLE(int deviceType, IntPtr nativeDevice, int[]? attribs); [GetProcAddress("eglReleaseDeviceANGLE", true)] public partial void ReleaseDeviceANGLE(IntPtr device); diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalD3D11Texture2D.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalD3D11Texture2D.cs index b0fa76fda2..6bbfde35e1 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalD3D11Texture2D.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalD3D11Texture2D.cs @@ -1,8 +1,5 @@ -#nullable enable using System; -using System.Threading; using Avalonia.OpenGL; -using Avalonia.OpenGL.Angle; using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Win32.DirectX; @@ -15,9 +12,12 @@ namespace Avalonia.Win32.OpenGl.Angle; internal class AngleExternalMemoryD3D11Texture2D : IGlExternalImageTexture { private readonly EglContext _context; - private ID3D11Texture2D _texture2D; - private EglSurface _eglSurface; - private IDXGIKeyedMutex _mutex; + private ID3D11Texture2D? _texture2D; + private EglSurface? _eglSurface; + private IDXGIKeyedMutex? _mutex; + + private IDXGIKeyedMutex Mutex + => _mutex ?? throw new ObjectDisposedException(nameof(AngleExternalMemoryD3D11Texture2D)); public unsafe AngleExternalMemoryD3D11Texture2D(EglContext context, ID3D11Texture2D texture2D, PlatformGraphicsExternalImageProperties props) { @@ -40,14 +40,14 @@ internal class AngleExternalMemoryD3D11Texture2D : IGlExternalImageTexture int temp = 0; gl.GenTextures(1, &temp); TextureId = temp; - gl.BindTexture(GlConsts.GL_TEXTURE_2D, TextureId); + gl.BindTexture(GL_TEXTURE_2D, TextureId); if (_context.Display.EglInterface.BindTexImage(_context.Display.Handle, _eglSurface.DangerousGetHandle(), EGL_BACK_BUFFER) == 0) throw OpenGlException.GetFormattedException("eglBindTexImage", _context.Display.EglInterface); } - + public void Dispose() { @@ -56,17 +56,15 @@ internal class AngleExternalMemoryD3D11Texture2D : IGlExternalImageTexture _context.GlInterface.DeleteTexture(TextureId); TextureId = 0; _eglSurface?.Dispose(); - _eglSurface = null!; + _eglSurface = null; _texture2D?.Dispose(); - _texture2D = null!; + _texture2D = null; _mutex?.Dispose(); - _mutex = null!; + _mutex = null; } - - public void AcquireKeyedMutex(uint key) => _mutex.AcquireSync(key, int.MaxValue); - - public void ReleaseKeyedMutex(uint key) => _mutex.ReleaseSync(key); + public void AcquireKeyedMutex(uint key) => Mutex.AcquireSync(key, int.MaxValue); + public void ReleaseKeyedMutex(uint key) => Mutex.ReleaseSync(key); public int TextureId { get; private set; } public int InternalFormat { get; } @@ -75,7 +73,7 @@ internal class AngleExternalMemoryD3D11Texture2D : IGlExternalImageTexture internal class AngleExternalMemoryD3D11ExportedTexture2D : AngleExternalMemoryD3D11Texture2D, IGlExportableExternalImageTexture { - static IPlatformHandle GetHandle(ID3D11Texture2D texture2D) + private static IPlatformHandle GetHandle(ID3D11Texture2D texture2D) { using var resource = texture2D.QueryInterface(); return new PlatformHandle(resource.SharedHandle, diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalObjectsFeature.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalObjectsFeature.cs index a7b52e953e..cad43e5d45 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalObjectsFeature.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalObjectsFeature.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; -using Avalonia.Controls.Documents; using Avalonia.OpenGL; using Avalonia.OpenGL.Egl; using Avalonia.Platform; @@ -93,8 +91,8 @@ internal class AngleExternalObjectsFeature : IGlContextExternalObjectsFeature, I return default; } - public byte[] DeviceLuid { get; } - public byte[] DeviceUuid { get; } + public byte[]? DeviceLuid { get; } + public byte[]? DeviceUuid => null; public void Dispose() { diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs index 53bf2fb8b1..770a27386f 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs @@ -1,4 +1,3 @@ -#nullable enable annotations using System; using System.Collections.Generic; using System.ComponentModel; @@ -97,7 +96,7 @@ namespace Avalonia.Win32.OpenGl.Angle DirectXUnmanagedMethods.D3D11CreateDevice(chosenAdapter?.GetNativeIntPtr() ?? IntPtr.Zero, D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_UNKNOWN, IntPtr.Zero, 0, featureLevels, (uint)featureLevels.Length, - 7, out pD3dDevice, out var featureLevel, null); + 7, out pD3dDevice, out _, null); if (pD3dDevice == IntPtr.Zero) diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs index a4d1ea457a..8ee6d286d1 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Avalonia.Logging; using Avalonia.OpenGL; @@ -12,11 +13,14 @@ namespace Avalonia.Win32.OpenGl.Angle; internal class AngleWin32PlatformGraphics : IPlatformGraphics, IPlatformGraphicsOpenGlContextFactory { private readonly Win32AngleEglInterface _egl; - private AngleWin32EglDisplay _sharedDisplay; - private EglContext _sharedContext; - public bool UsesSharedContext => PlatformApi == AngleOptions.PlatformApi.DirectX9; + private readonly AngleWin32EglDisplay? _sharedDisplay; + private EglContext? _sharedContext; + + [MemberNotNullWhen(true, nameof(_sharedDisplay))] + public bool UsesSharedContext => _sharedDisplay is not null; + + public AngleOptions.PlatformApi PlatformApi { get; } - public AngleOptions.PlatformApi PlatformApi { get; } = AngleOptions.PlatformApi.DirectX11; public IPlatformGraphicsContext CreateContext() { if (UsesSharedContext) @@ -72,7 +76,7 @@ internal class AngleWin32PlatformGraphics : IPlatformGraphics, IPlatformGraphics } - public static AngleWin32PlatformGraphics TryCreate(AngleOptions options) + public static AngleWin32PlatformGraphics? TryCreate(AngleOptions? options) { Win32AngleEglInterface egl; try @@ -109,7 +113,7 @@ internal class AngleWin32PlatformGraphics : IPlatformGraphics, IPlatformGraphics } else { - AngleWin32EglDisplay sharedDisplay = null; + AngleWin32EglDisplay? sharedDisplay = null; try { sharedDisplay = AngleWin32EglDisplay.CreateD3D9Display(egl); diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs b/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs index b4002d8bc7..999818d709 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs @@ -7,22 +7,22 @@ using Avalonia.Platform; using Avalonia.Reactive; using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; -using static Avalonia.Win32.OpenGl.WglConsts; namespace Avalonia.Win32.OpenGl { - class WglContext : IGlContext + internal class WglContext : IGlContext { - private object _lock = new object(); - private readonly WglContext _sharedWith; + private readonly object _lock = new(); + private readonly WglContext? _sharedWith; private readonly IntPtr _context; private readonly IntPtr _hWnd; private readonly IntPtr _dc; private readonly int _pixelFormat; private readonly PixelFormatDescriptor _formatDescriptor; + public IntPtr Handle => _context; - public WglContext(WglContext sharedWith, GlVersion version, IntPtr context, IntPtr hWnd, IntPtr dc, int pixelFormat, + public WglContext(WglContext? sharedWith, GlVersion version, IntPtr context, IntPtr hWnd, IntPtr dc, int pixelFormat, PixelFormatDescriptor formatDescriptor) { Version = version; @@ -54,7 +54,7 @@ namespace Avalonia.Win32.OpenGl public GlVersion Version { get; } public GlInterface GlInterface { get; } - public int SampleCount { get; } + public int SampleCount => 0; public int StencilSize { get; } private bool IsCurrent => wglGetCurrentContext() == _context && wglGetCurrentDC() == _dc; @@ -97,12 +97,12 @@ namespace Avalonia.Win32.OpenGl } public bool CanCreateSharedContext => true; - public IGlContext CreateSharedContext(IEnumerable preferredVersions = null) + public IGlContext? CreateSharedContext(IEnumerable? preferredVersions = null) { var versions = preferredVersions?.Append(Version).ToArray() ?? new[] { Version }; return WglDisplay.CreateContext(versions, _sharedWith ?? this); } - public object TryGetFeature(Type featureType) => null; + public object? TryGetFeature(Type featureType) => null; } } diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs index 9a1963a97a..e207bc23ab 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Avalonia.OpenGL; using Avalonia.Threading; @@ -19,29 +20,31 @@ namespace Avalonia.Win32.OpenGl private static int _defaultPixelFormat; public static IntPtr OpenGl32Handle = LoadLibrary("opengl32"); - private delegate bool WglChoosePixelFormatARBDelegate(IntPtr hdc, int[] piAttribIList, float[] pfAttribFList, + private delegate bool WglChoosePixelFormatARBDelegate(IntPtr hdc, int[]? piAttribIList, float[]? pfAttribFList, int nMaxFormats, int[] piFormats, out int nNumFormats); - private static WglChoosePixelFormatARBDelegate WglChoosePixelFormatArb; + private static WglChoosePixelFormatARBDelegate? s_wglChoosePixelFormatArb; - private delegate IntPtr WglCreateContextAttribsARBDelegate(IntPtr hDC, IntPtr hShareContext, int[] attribList); + private delegate IntPtr WglCreateContextAttribsARBDelegate(IntPtr hDC, IntPtr hShareContext, int[]? attribList); - private static WglCreateContextAttribsARBDelegate WglCreateContextAttribsArb; + private static WglCreateContextAttribsARBDelegate? s_wglCreateContextAttribsArb; private delegate void GlDebugMessageCallbackDelegate(IntPtr callback, IntPtr userParam); - private static GlDebugMessageCallbackDelegate GlDebugMessageCallback; + private static GlDebugMessageCallbackDelegate? s_glDebugMessageCallback; private delegate void DebugCallbackDelegate(int source, int type, int id, int severity, int len, IntPtr message, IntPtr userParam); - - static bool Initialize() - { - if (!_initialized.HasValue) - _initialized = InitializeCore(); - return _initialized.Value; - } - static bool InitializeCore() + + [MemberNotNullWhen(true, nameof(s_wglChoosePixelFormatArb))] + [MemberNotNullWhen(true, nameof(s_wglCreateContextAttribsArb))] + [MemberNotNullWhen(true, nameof(s_glDebugMessageCallback))] + private static bool Initialize() => _initialized ??= InitializeCore(); + + [MemberNotNullWhen(true, nameof(s_wglChoosePixelFormatArb))] + [MemberNotNullWhen(true, nameof(s_wglCreateContextAttribsArb))] + [MemberNotNullWhen(true, nameof(s_glDebugMessageCallback))] + private static bool InitializeCore() { Dispatcher.UIThread.VerifyAccess(); _bootstrapWindow = WglGdiResourceManager.CreateOffscreenWindow(); @@ -64,20 +67,20 @@ namespace Avalonia.Win32.OpenGl return false; wglMakeCurrent(_bootstrapDc, _bootstrapContext); - WglCreateContextAttribsArb = Marshal.GetDelegateForFunctionPointer( + s_wglCreateContextAttribsArb = Marshal.GetDelegateForFunctionPointer( wglGetProcAddress("wglCreateContextAttribsARB")); - WglChoosePixelFormatArb = + s_wglChoosePixelFormatArb = Marshal.GetDelegateForFunctionPointer( wglGetProcAddress("wglChoosePixelFormatARB")); - GlDebugMessageCallback = + s_glDebugMessageCallback = Marshal.GetDelegateForFunctionPointer( wglGetProcAddress("glDebugMessageCallback")); var formats = new int[1]; - WglChoosePixelFormatArb(_bootstrapDc, new int[] + s_wglChoosePixelFormatArb(_bootstrapDc, new int[] { WGL_DRAW_TO_WINDOW_ARB, 1, WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, @@ -106,12 +109,12 @@ namespace Avalonia.Win32.OpenGl Console.Error.WriteLine(err); } - public static WglContext CreateContext(GlVersion[] versions, IGlContext share) + public static WglContext? CreateContext(GlVersion[] versions, IGlContext? share) { if (!Initialize()) return null; - var shareContext = (WglContext)share; + var shareContext = share as WglContext; using (new WglRestoreContext(_bootstrapDc, _bootstrapContext, null)) { @@ -125,7 +128,7 @@ namespace Avalonia.Win32.OpenGl IntPtr context; using (shareContext?.Lock()) { - context = WglCreateContextAttribsArb(dc, shareContext?.Handle ?? IntPtr.Zero, + context = s_wglCreateContextAttribsArb(dc, shareContext?.Handle ?? IntPtr.Zero, new[] { // major @@ -142,7 +145,7 @@ namespace Avalonia.Win32.OpenGl } using(new WglRestoreContext(dc, context, null)) - GlDebugMessageCallback(Marshal.GetFunctionPointerForDelegate(_debugCallback), IntPtr.Zero); + s_glDebugMessageCallback(Marshal.GetFunctionPointerForDelegate(_debugCallback), IntPtr.Zero); if (context != IntPtr.Zero) return new WglContext(shareContext, version, context, window, dc, _defaultPixelFormat, _defaultPfd); diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglGdiResourceManager.cs b/src/Windows/Avalonia.Win32/OpenGl/WglGdiResourceManager.cs index ed40488c70..86143d078d 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglGdiResourceManager.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglGdiResourceManager.cs @@ -17,45 +17,69 @@ namespace Avalonia.Win32.OpenGl; internal class WglGdiResourceManager { - class GetDCOp + private class GetDCOp { - public IntPtr Window; - public TaskCompletionSource Result; + public readonly IntPtr Window; + public readonly TaskCompletionSource Result; + + public GetDCOp(IntPtr window, TaskCompletionSource result) + { + Window = window; + Result = result; + } } - class ReleaseDCOp + private class ReleaseDCOp { - public IntPtr Window; - public IntPtr DC; - public TaskCompletionSource Result; + public readonly IntPtr Window; + public readonly IntPtr DC; + public readonly TaskCompletionSource Result; + + public ReleaseDCOp(IntPtr window, IntPtr dc, TaskCompletionSource result) + { + Window = window; + DC = dc; + Result = result; + } } - - class CreateWindowOp + + private class CreateWindowOp { - public TaskCompletionSource Result; + public readonly TaskCompletionSource Result; + + public CreateWindowOp(TaskCompletionSource result) + { + Result = result; + } } - class DestroyWindowOp + private class DestroyWindowOp { - public IntPtr Window; - public TaskCompletionSource Result; + public readonly IntPtr Window; + public readonly TaskCompletionSource Result; + + public DestroyWindowOp(IntPtr window, TaskCompletionSource result) + { + Window = window; + Result = result; + } } - private static readonly Queue s_Queue = new(); - private static readonly AutoResetEvent s_Event = new(false); - private static readonly ushort s_WindowClass; + private static readonly Queue s_queue = new(); + private static readonly AutoResetEvent s_event = new(false); + private static readonly ushort s_windowClass; private static readonly UnmanagedMethods.WndProc s_wndProcDelegate = WndProc; - static void Worker() + private static void Worker() { while (true) { - s_Event.WaitOne(); - lock (s_Queue) + s_event.WaitOne(); + lock (s_queue) { - if(s_Queue.Count == 0) + if(s_queue.Count == 0) continue; - var job = s_Queue.Dequeue(); + var job = s_queue.Dequeue(); if (job is GetDCOp getDc) getDc.Result.TrySetResult(UnmanagedMethods.GetDC(getDc.Window)); else if (job is ReleaseDCOp releaseDc) @@ -66,7 +90,7 @@ internal class WglGdiResourceManager else if (job is CreateWindowOp createWindow) createWindow.Result.TrySetResult(UnmanagedMethods.CreateWindowEx( 0, - s_WindowClass, + s_windowClass, null, (int)UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW, 0, @@ -97,16 +121,15 @@ internal class WglGdiResourceManager style = (int)UnmanagedMethods.ClassStyles.CS_OWNDC }; - s_WindowClass = UnmanagedMethods.RegisterClassEx(ref wndClassEx); + s_windowClass = UnmanagedMethods.RegisterClassEx(ref wndClassEx); var th = new Thread(Worker) { IsBackground = true, Name = "Win32 OpenGL HDC manager" }; // This makes CLR to automatically pump the event queue from WaitOne th.SetApartmentState(ApartmentState.STA); th.Start(); } - - - - static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + + + private static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); } @@ -114,53 +137,36 @@ internal class WglGdiResourceManager public static IntPtr CreateOffscreenWindow() { var tcs = new TaskCompletionSource(); - lock(s_Queue) - s_Queue.Enqueue(new CreateWindowOp() - { - Result = tcs - }); - s_Event.Set(); + lock (s_queue) + s_queue.Enqueue(new CreateWindowOp(tcs)); + s_event.Set(); return tcs.Task.Result; } public static IntPtr GetDC(IntPtr hWnd) { var tcs = new TaskCompletionSource(); - lock(s_Queue) - s_Queue.Enqueue(new GetDCOp - { - Window = hWnd, - Result = tcs - }); - s_Event.Set(); + lock (s_queue) + s_queue.Enqueue(new GetDCOp(hWnd, tcs)); + s_event.Set(); return tcs.Task.Result; } public static void ReleaseDC(IntPtr hWnd, IntPtr hDC) { - var tcs = new TaskCompletionSource(); - lock(s_Queue) - s_Queue.Enqueue(new ReleaseDCOp() - { - Window = hWnd, - DC = hDC, - Result = tcs - }); - s_Event.Set(); + var tcs = new TaskCompletionSource(); + lock (s_queue) + s_queue.Enqueue(new ReleaseDCOp(hWnd, hDC, tcs)); + s_event.Set(); tcs.Task.Wait(); } public static void DestroyWindow(IntPtr hWnd) { - var tcs = new TaskCompletionSource(); - lock(s_Queue) - s_Queue.Enqueue(new DestroyWindowOp() - { - Window = hWnd, - Result = tcs - }); - s_Event.Set(); + var tcs = new TaskCompletionSource(); + lock (s_queue) + s_queue.Enqueue(new DestroyWindowOp(hWnd, tcs)); + s_event.Set(); tcs.Task.Wait(); - } } diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs b/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs index 39dd330d52..61b932e9c7 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs @@ -6,27 +6,31 @@ using Avalonia.Platform; namespace Avalonia.Win32.OpenGl { - class WglPlatformOpenGlInterface : IPlatformGraphics + internal class WglPlatformOpenGlInterface : IPlatformGraphics { public WglContext PrimaryContext { get; } public bool UsesSharedContext => false; IPlatformGraphicsContext IPlatformGraphics.CreateContext() => CreateContext(); public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException(); - - public IGlContext CreateContext() => WglDisplay.CreateContext(new[] { PrimaryContext.Version }, null); - private WglPlatformOpenGlInterface(WglContext primary) + public IGlContext CreateContext() + => WglDisplay.CreateContext(new[] { PrimaryContext.Version }, null) + ?? throw new OpenGlException("Unable to create additional WGL context"); + + private WglPlatformOpenGlInterface(WglContext primary) { PrimaryContext = primary; } - public static WglPlatformOpenGlInterface TryCreate() + public static WglPlatformOpenGlInterface? TryCreate() { try { var opts = AvaloniaLocator.Current.GetService() ?? new Win32PlatformOptions(); - var primary = WglDisplay.CreateContext(opts.WglProfiles.ToArray(), null); - return new WglPlatformOpenGlInterface(primary); + if (WglDisplay.CreateContext(opts.WglProfiles.ToArray(), null) is { } primary) + { + return new WglPlatformOpenGlInterface(primary); + } } catch (Exception e) { diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs b/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs index b145ffbb37..ee0934af7d 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs @@ -8,11 +8,11 @@ namespace Avalonia.Win32.OpenGl { internal class WglRestoreContext : IDisposable { - private readonly object _monitor; + private readonly object? _monitor; private readonly IntPtr _oldDc; private readonly IntPtr _oldContext; - public WglRestoreContext(IntPtr gc, IntPtr context, object monitor, bool takeMonitor = true) + public WglRestoreContext(IntPtr gc, IntPtr context, object? monitor, bool takeMonitor = true) { _monitor = monitor; _oldDc = wglGetCurrentDC(); diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index 3a56af12f0..75c1a2d564 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -5,9 +5,9 @@ using Avalonia.Win32.Interop; namespace Avalonia.Win32 { - class PopupImpl : WindowImpl, IPopupImpl + internal class PopupImpl : WindowImpl, IPopupImpl { - private readonly IWindowBaseImpl _parent; + private readonly IWindowBaseImpl? _parent; private bool _dropShadowHint = true; private Size? _maxAutoSize; @@ -92,7 +92,7 @@ namespace Avalonia.Win32 IntPtr.Zero); s_parentHandle = IntPtr.Zero; - PopupImpl.EnableBoxShadow(result, _dropShadowHint); + EnableBoxShadow(result, _dropShadowHint); return result; } @@ -113,7 +113,7 @@ namespace Avalonia.Win32 // This is needed because we are calling virtual methods from constructors // One fabulous design decision leads to another, I guess - static IWindowBaseImpl SaveParentHandle(IWindowBaseImpl parent) + private static IWindowBaseImpl SaveParentHandle(IWindowBaseImpl parent) { s_parentHandle = parent.Handle.Handle; return parent; @@ -126,7 +126,7 @@ namespace Avalonia.Win32 } - private PopupImpl(IWindowBaseImpl parent, bool dummy) : base() + private PopupImpl(IWindowBaseImpl parent, bool dummy) { _parent = parent; PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize)); @@ -159,10 +159,7 @@ namespace Avalonia.Win32 { _dropShadowHint = enabled; - if (Handle != null) - { - PopupImpl.EnableBoxShadow(Handle.Handle, enabled); - } + EnableBoxShadow(Handle.Handle, enabled); } public IPopupPositioner PopupPositioner { get; } diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index f3754cd58f..69431c7ff6 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using Avalonia.Metadata; using Avalonia.Platform; -using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32 @@ -11,12 +10,15 @@ namespace Avalonia.Win32 [Unstable] public class ScreenImpl : IScreenImpl { + private Screen[]? _allScreens; + + /// public int ScreenCount { get => GetSystemMetrics(SystemMetric.SM_CMONITORS); } - private Screen[] _allScreens; + /// public IReadOnlyList AllScreens { get @@ -38,7 +40,7 @@ namespace Avalonia.Win32 if (method != IntPtr.Zero) { GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var x, out _); - dpi = (double)x; + dpi = x; } else { @@ -74,7 +76,8 @@ namespace Avalonia.Win32 _allScreens = null; } - public Screen ScreenFromWindow(IWindowBaseImpl window) + /// + public Screen? ScreenFromWindow(IWindowBaseImpl window) { var handle = window.Handle.Handle; @@ -83,7 +86,8 @@ namespace Avalonia.Win32 return FindScreenByHandle(monitor); } - public Screen ScreenFromPoint(PixelPoint point) + /// + public Screen? ScreenFromPoint(PixelPoint point) { var monitor = MonitorFromPoint(new POINT { @@ -94,7 +98,8 @@ namespace Avalonia.Win32 return FindScreenByHandle(monitor); } - public Screen ScreenFromRect(PixelRect rect) + /// + public Screen? ScreenFromRect(PixelRect rect) { var monitor = MonitorFromRect(new RECT { @@ -107,7 +112,7 @@ namespace Avalonia.Win32 return FindScreenByHandle(monitor); } - private Screen FindScreenByHandle(IntPtr handle) + private Screen? FindScreenByHandle(IntPtr handle) { return AllScreens.Cast().FirstOrDefault(m => m.Handle == handle); } diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index fe920ff1a1..8a10c6d2e1 100644 --- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs +++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs @@ -11,8 +11,6 @@ using Avalonia.Styling; using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; -#nullable enable - namespace Avalonia.Win32 { [Unstable] @@ -25,9 +23,9 @@ namespace Avalonia.Win32 private IconImpl? _icon; private string? _tooltipText; private readonly Win32NativeToManagedMenuExporter _exporter; - private static readonly Dictionary s_trayIcons = new Dictionary(); + private static readonly Dictionary s_trayIcons = new(); private bool _disposedValue; - private static readonly uint WM_TASKBARCREATED = UnmanagedMethods.RegisterWindowMessage("TaskbarCreated"); + private static readonly uint WM_TASKBARCREATED = RegisterWindowMessage("TaskbarCreated"); public TrayIconImpl() { @@ -62,17 +60,20 @@ namespace Avalonia.Win32 } } + /// public void SetIcon(IWindowIconImpl? icon) { _icon = icon as IconImpl; UpdateIcon(); } + /// public void SetIsVisible(bool visible) { UpdateIcon(!visible); } + /// public void SetToolTipText(string? text) { _tooltipText = text; @@ -209,8 +210,11 @@ namespace Avalonia.Win32 private void MoveResize(PixelPoint position, Size size, double scaling) { - PlatformImpl!.Move(position); - PlatformImpl!.Resize(size, PlatformResizeReason.Layout); + if (PlatformImpl is { } platformImpl) + { + platformImpl.Move(position); + platformImpl.Resize(size, PlatformResizeReason.Layout); + } } protected override void ArrangeCore(Rect finalRect) @@ -221,7 +225,7 @@ namespace Avalonia.Win32 { Anchor = PopupAnchor.TopLeft, Gravity = PopupGravity.BottomRight, - AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.Scaling, new Size(1, 1)), + AnchorRectangle = new Rect(Position.ToPoint(Screens.Primary?.Scaling ?? 1.0), new Size(1, 1)), Size = finalRect.Size, ConstraintAdjustment = PopupPositionerConstraintAdjustment.FlipX | PopupPositionerConstraintAdjustment.FlipY, }); @@ -247,18 +251,22 @@ namespace Avalonia.Win32 { get { - var point = _hiddenWindow.Screens.Primary.Bounds.TopLeft; - var size = _hiddenWindow.Screens.Primary.Bounds.Size; - return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.Scaling, size.Height * _hiddenWindow.Screens.Primary.Scaling); + if (_hiddenWindow.Screens.Primary is { } screen) + { + var point = screen.Bounds.TopLeft; + var size = screen.Bounds.Size; + return new Rect(point.X, point.Y, size.Width * screen.Scaling, size.Height * screen.Scaling); + } + return default; } } public void MoveAndResize(Point devicePoint, Size virtualSize) { - _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.Scaling); + _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, Scaling); } - public double Scaling => _hiddenWindow.Screens.Primary.Scaling; + public double Scaling => _hiddenWindow.Screens.Primary?.Scaling ?? 1.0; } } diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index ab6de1a027..c26ce5fd45 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs @@ -1,6 +1,4 @@ using Avalonia.OpenGL; -using Avalonia.OpenGL.Angle; -using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Win32.DirectX; using Avalonia.Win32.OpenGl; @@ -11,16 +9,19 @@ namespace Avalonia.Win32 { static class Win32GlManager { - - public static IPlatformGraphics Initialize() + public static IPlatformGraphics? Initialize() { var gl = InitializeCore(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl); + + if (gl is not null) + { + AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl); + } + return gl; } - static IPlatformGraphics InitializeCore() + private static IPlatformGraphics? InitializeCore() { var opts = AvaloniaLocator.Current.GetService() ?? new Win32PlatformOptions(); @@ -55,7 +56,5 @@ namespace Avalonia.Win32 return null; } - - } } diff --git a/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs b/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs index fd05e780bf..620cd64e79 100644 --- a/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs +++ b/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs @@ -1,13 +1,13 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Avalonia.Controls.Platform; using Avalonia.Platform; -using Avalonia.VisualTree; using Avalonia.Win32.Interop; namespace Avalonia.Win32 { - class Win32NativeControlHost : INativeControlHostImpl + internal class Win32NativeControlHost : INativeControlHostImpl { private readonly bool _useLayeredWindow; public WindowImpl Window { get; } @@ -18,7 +18,7 @@ namespace Avalonia.Win32 Window = window; } - void AssertCompatible(IPlatformHandle handle) + private void AssertCompatible(IPlatformHandle handle) { if (!IsCompatibleWith(handle)) throw new ArgumentException($"Don't know what to do with {handle.HandleDescriptor}"); @@ -33,7 +33,7 @@ namespace Avalonia.Win32 public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func create) { var holder = new DumbWindow(_useLayeredWindow, Window.Handle.Handle); - Win32NativeControlAttachment attachment = null; + Win32NativeControlAttachment? attachment = null; try { var child = create(holder); @@ -46,7 +46,7 @@ namespace Avalonia.Win32 catch { attachment?.Dispose(); - holder?.Destroy(); + holder.Destroy(); throw; } } @@ -60,13 +60,13 @@ namespace Avalonia.Win32 public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == "HWND"; - class DumbWindow : IDisposable, INativeControlHostDestroyableControlHandle + private class DumbWindow : IDisposable, INativeControlHostDestroyableControlHandle { public IntPtr Handle { get;} public string HandleDescriptor => "HWND"; public void Destroy() => Dispose(); - UnmanagedMethods.WndProc _wndProcDelegate; + private readonly UnmanagedMethods.WndProc _wndProcDelegate; private readonly string _className; public DumbWindow(bool layered = false, IntPtr? parent = null) @@ -103,9 +103,7 @@ namespace Avalonia.Win32 UnmanagedMethods.LayeredWindowFlags.LWA_ALPHA); } - - - protected virtual unsafe IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + private static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); } @@ -128,11 +126,11 @@ namespace Avalonia.Win32 } } - class Win32NativeControlAttachment : INativeControlHostControlTopLevelAttachment + private class Win32NativeControlAttachment : INativeControlHostControlTopLevelAttachment { - private DumbWindow _holder; - private IPlatformHandle _child; - private Win32NativeControlHost _attachedTo; + private DumbWindow? _holder; + private IPlatformHandle? _child; + private Win32NativeControlHost? _attachedTo; public Win32NativeControlAttachment(DumbWindow holder, IPlatformHandle child) { @@ -142,7 +140,8 @@ namespace Avalonia.Win32 UnmanagedMethods.ShowWindow(child.Handle, UnmanagedMethods.ShowWindowCommand.Show); } - void CheckDisposed() + [MemberNotNull(nameof(_holder))] + private void CheckDisposed() { if (_holder == null) throw new ObjectDisposedException(nameof(Win32NativeControlAttachment)); @@ -158,13 +157,13 @@ namespace Avalonia.Win32 _attachedTo = null; } - public INativeControlHostImpl AttachedTo + public INativeControlHostImpl? AttachedTo { get => _attachedTo; set { CheckDisposed(); - _attachedTo = (Win32NativeControlHost) value; + _attachedTo = value as Win32NativeControlHost; if (_attachedTo == null) { UnmanagedMethods.ShowWindow(_holder.Handle, UnmanagedMethods.ShowWindowCommand.Hide); @@ -179,6 +178,7 @@ namespace Avalonia.Win32 public void HideWithSize(Size size) { + CheckDisposed(); UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero, -100, -100, 1, 1, UnmanagedMethods.SetWindowPosFlags.SWP_HIDEWINDOW | @@ -198,8 +198,12 @@ namespace Avalonia.Win32 bounds *= _attachedTo.Window.RenderScaling; var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width), Math.Max(1, (int)bounds.Height)); - - UnmanagedMethods.MoveWindow(_child.Handle, 0, 0, pixelRect.Width, pixelRect.Height, true); + + if (_child is not null) + { + UnmanagedMethods.MoveWindow(_child.Handle, 0, 0, pixelRect.Width, pixelRect.Height, true); + } + UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero, pixelRect.X, pixelRect.Y, pixelRect.Width, pixelRect.Height, UnmanagedMethods.SetWindowPosFlags.SWP_SHOWWINDOW diff --git a/src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs b/src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs index fdf36206d0..46cdf66653 100644 --- a/src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs +++ b/src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs @@ -1,12 +1,7 @@ -using System.Collections.Generic; -using Avalonia.Reactive; +using Avalonia.Reactive; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Platform; -using Avalonia.Media.Imaging; -using Avalonia.Utilities; - -#nullable enable namespace Avalonia.Win32 { @@ -19,7 +14,7 @@ namespace Avalonia.Win32 _nativeMenu = nativeMenu; } - private AvaloniaList Populate(NativeMenu nativeMenu) + private static AvaloniaList Populate(NativeMenu nativeMenu) { var result = new AvaloniaList(); diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index c34be9008a..4b327ed02a 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -17,12 +17,10 @@ using Avalonia.Rendering.Composition; using Avalonia.Threading; using Avalonia.Utilities; using Avalonia.Win32.Input; -using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia { -#nullable enable public static class Win32ApplicationExtensions { public static AppBuilder UseWin32(this AppBuilder builder) @@ -106,17 +104,19 @@ namespace Avalonia public IPlatformGraphics? CustomPlatformGraphics { get; set; } } } -#nullable restore namespace Avalonia.Win32 { public class Win32Platform : IPlatformThreadingInterface, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl { - private static readonly Win32Platform s_instance = new Win32Platform(); - private static Thread _uiThread; - private UnmanagedMethods.WndProc _wndProcDelegate; + private static readonly Win32Platform s_instance = new(); + private static Thread? s_uiThread; + private static Win32PlatformOptions? s_options; + private static Compositor? s_compositor; + + private WndProc? _wndProcDelegate; private IntPtr _hwnd; - private readonly List _delegates = new List(); + private readonly List _delegates = new(); public Win32Platform() { @@ -135,9 +135,12 @@ namespace Avalonia.Win32 public static Version WindowsVersion { get; } = RtlGetVersion(); internal static bool UseOverlayPopups => Options.OverlayPopups; - public static Win32PlatformOptions Options { get; private set; } - internal static Compositor Compositor { get; private set; } + public static Win32PlatformOptions Options + => s_options ?? throw new InvalidOperationException($"{nameof(Win32Platform)} hasn't been initialized"); + + internal static Compositor Compositor + => s_compositor ?? throw new InvalidOperationException($"{nameof(Win32Platform)} hasn't been initialized"); public static void Initialize() { @@ -146,7 +149,7 @@ namespace Avalonia.Win32 public static void Initialize(Win32PlatformOptions options) { - Options = options; + s_options = options; var renderTimer = options.ShouldRenderOnUIThread ? new UiThreadRenderTimer(60) : new DefaultRenderTimer(60); AvaloniaLocator.CurrentMutable @@ -171,26 +174,24 @@ namespace Avalonia.Win32 .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()) .Bind().ToConstant(s_instance); - _uiThread = Thread.CurrentThread; + s_uiThread = Thread.CurrentThread; - var platformGraphics = options?.CustomPlatformGraphics + var platformGraphics = options.CustomPlatformGraphics ?? Win32GlManager.Initialize(); if (OleContext.Current != null) AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), platformGraphics); + s_compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), platformGraphics); } public bool HasMessages() { - UnmanagedMethods.MSG msg; - return PeekMessage(out msg, IntPtr.Zero, 0, 0, 0); + return PeekMessage(out _, IntPtr.Zero, 0, 0, 0); } public void ProcessMessage() { - if (GetMessage(out var msg, IntPtr.Zero, 0, 0) > -1) { TranslateMessage(ref msg); @@ -222,8 +223,7 @@ namespace Avalonia.Win32 public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action callback) { - UnmanagedMethods.TimerProc timerDelegate = - (hWnd, uMsg, nIDEvent, dwTime) => callback(); + TimerProc timerDelegate = (_, _, _, _) => callback(); IntPtr handle = SetTimer( IntPtr.Zero, @@ -253,11 +253,11 @@ namespace Avalonia.Win32 new IntPtr(SignalL)); } - public bool CurrentThreadIsLoopThread => _uiThread == Thread.CurrentThread; + public bool CurrentThreadIsLoopThread => s_uiThread == Thread.CurrentThread; - public event Action Signaled; + public event Action? Signaled; - public event EventHandler ShutdownRequested; + public event EventHandler? ShutdownRequested; [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) @@ -301,11 +301,11 @@ namespace Avalonia.Win32 private void CreateMessageWindow() { // Ensure that the delegate doesn't get garbage collected by storing it as a field. - _wndProcDelegate = new UnmanagedMethods.WndProc(WndProc); + _wndProcDelegate = WndProc; - UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX + WNDCLASSEX wndClassEx = new WNDCLASSEX { - cbSize = Marshal.SizeOf(), + cbSize = Marshal.SizeOf(), lpfnWndProc = _wndProcDelegate, hInstance = GetModuleHandle(null), lpszClassName = "AvaloniaMessageWindow " + Guid.NewGuid(), @@ -326,7 +326,7 @@ namespace Avalonia.Win32 } } - public ITrayIconImpl CreateTrayIcon () + public ITrayIconImpl CreateTrayIcon() { return new TrayIconImpl(); } diff --git a/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs b/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs index 0310c4bfad..c5f7f17b32 100644 --- a/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs +++ b/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs @@ -1,8 +1,6 @@ using System; -using System.Runtime.InteropServices; using Avalonia.Input; using Avalonia.Platform; -using Avalonia.Win32.Interop; using Avalonia.Win32.WinRT; using static Avalonia.Win32.Interop.UnmanagedMethods; @@ -10,14 +8,14 @@ namespace Avalonia.Win32; internal class Win32PlatformSettings : DefaultPlatformSettings { - private PlatformColorValues _lastColorValues; + private PlatformColorValues? _lastColorValues; public override Size GetTapSize(PointerType type) { return type switch { PointerType.Touch => new(10, 10), - _ => new(GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXDRAG), GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDRAG)), + _ => new(GetSystemMetrics(SystemMetric.SM_CXDRAG), GetSystemMetrics(SystemMetric.SM_CYDRAG)), }; } @@ -26,7 +24,7 @@ internal class Win32PlatformSettings : DefaultPlatformSettings return type switch { PointerType.Touch => new(16, 16), - _ => new(GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXDOUBLECLK), GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDOUBLECLK)), + _ => new(GetSystemMetrics(SystemMetric.SM_CXDOUBLECLK), GetSystemMetrics(SystemMetric.SM_CYDOUBLECLK)), }; } diff --git a/src/Windows/Avalonia.Win32/Win32StorageProvider.cs b/src/Windows/Avalonia.Win32/Win32StorageProvider.cs index 2da642bd19..2fd49c8b09 100644 --- a/src/Windows/Avalonia.Win32/Win32StorageProvider.cs +++ b/src/Windows/Avalonia.Win32/Win32StorageProvider.cs @@ -1,13 +1,10 @@ -#nullable enable - -using System; +using System; using System.Linq; using System.Collections.Generic; using System.IO; using System.ComponentModel; using System.Runtime.InteropServices; using System.Threading.Tasks; -using Avalonia.MicroCom; using Avalonia.Platform.Storage; using Avalonia.Platform.Storage.FileIO; using Avalonia.Win32.Interop; @@ -146,7 +143,7 @@ namespace Avalonia.Win32 } } - var showResult = frm.Show(_windowImpl.Handle!.Handle); + var showResult = frm.Show(_windowImpl.Handle.Handle); if ((uint)showResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED) { @@ -188,7 +185,7 @@ namespace Avalonia.Win32 var message = new Win32Exception(ex.HResult).Message; throw new COMException(message, ex); } - })!; + }); } @@ -220,7 +217,6 @@ namespace Avalonia.Win32 } var size = Marshal.SizeOf(); - var arr = new byte[size]; var resultArr = new byte[size * filters.Count]; for (int i = 0; i < filters.Count; i++) @@ -236,7 +232,7 @@ namespace Avalonia.Win32 { var filterStr = new UnmanagedMethods.COMDLG_FILTERSPEC { - pszName = filter.Name ?? string.Empty, + pszName = filter.Name, pszSpec = string.Join(";", filter.Patterns) }; diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs index 32f44842f8..1a922b4acd 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs @@ -1,14 +1,13 @@ using System; using System.Linq; using System.Runtime.InteropServices; -using Avalonia.MicroCom; using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition { - abstract class WinUIEffectBase : WinRTInspectable, IGraphicsEffect, IGraphicsEffectSource, IGraphicsEffectD2D1Interop + internal abstract class WinUIEffectBase : WinRTInspectable, IGraphicsEffect, IGraphicsEffectSource, IGraphicsEffectD2D1Interop { - private IGraphicsEffectSource[] _sources; + private IGraphicsEffectSource[]? _sources; public WinUIEffectBase(params IGraphicsEffectSource[] _sources) { @@ -32,7 +31,7 @@ namespace Avalonia.Win32.WinRT.Composition throw new COMException("Not supported", unchecked((int)0x80004001)); public abstract uint PropertyCount { get; } - public abstract IPropertyValue GetProperty(uint index); + public abstract IPropertyValue? GetProperty(uint index); public IGraphicsEffectSource GetSource(uint index) { @@ -53,14 +52,14 @@ namespace Avalonia.Win32.WinRT.Composition _sources = null; } } - - class WinUIGaussianBlurEffect : WinUIEffectBase + + internal class WinUIGaussianBlurEffect : WinUIEffectBase { public WinUIGaussianBlurEffect(IGraphicsEffectSource source) : base(source) { } - enum D2D1_GAUSSIANBLUR_OPTIMIZATION + private enum D2D1_GAUSSIANBLUR_OPTIMIZATION { D2D1_GAUSSIANBLUR_OPTIMIZATION_SPEED, D2D1_GAUSSIANBLUR_OPTIMIZATION_BALANCED, @@ -68,14 +67,14 @@ namespace Avalonia.Win32.WinRT.Composition D2D1_GAUSSIANBLUR_OPTIMIZATION_FORCE_DWORD }; - enum D2D1_BORDER_MODE + private enum D2D1_BORDER_MODE { D2D1_BORDER_MODE_SOFT, D2D1_BORDER_MODE_HARD, D2D1_BORDER_MODE_FORCE_DWORD }; - enum D2D1GaussianBlurProp + private enum D2D1GaussianBlurProp { D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, D2D1_GAUSSIANBLUR_PROP_OPTIMIZATION, @@ -87,7 +86,7 @@ namespace Avalonia.Win32.WinRT.Composition public override uint PropertyCount => 3; - public override IPropertyValue GetProperty(uint index) + public override IPropertyValue? GetProperty(uint index) { switch ((D2D1GaussianBlurProp)index) { @@ -105,14 +104,14 @@ namespace Avalonia.Win32.WinRT.Composition return null; } } - - class SaturationEffect : WinUIEffectBase + + internal class SaturationEffect : WinUIEffectBase { public SaturationEffect(IGraphicsEffectSource source) : base(source) { } - enum D2D1_SATURATION_PROP + private enum D2D1_SATURATION_PROP { D2D1_SATURATION_PROP_SATURATION, D2D1_SATURATION_PROP_FORCE_DWORD @@ -122,7 +121,7 @@ namespace Avalonia.Win32.WinRT.Composition public override uint PropertyCount => 1; - public override IPropertyValue GetProperty(uint index) + public override IPropertyValue? GetProperty(uint index) { switch ((D2D1_SATURATION_PROP)index) { diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs index 5be25a3d4b..2f22ba99f9 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs @@ -3,7 +3,6 @@ using System.Numerics; using System.Threading; using Avalonia.OpenGL.Egl; using Avalonia.Reactive; -using Avalonia.Win32.Interop; using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition; @@ -12,8 +11,8 @@ internal class WinUiCompositedWindow : IDisposable { public EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo WindowInfo { get; } private readonly WinUiCompositionShared _shared; - private readonly ICompositionRoundedRectangleGeometry _compositionRoundedRectangleGeometry; - private readonly IVisual _mica; + private readonly ICompositionRoundedRectangleGeometry? _compositionRoundedRectangleGeometry; + private readonly IVisual? _mica; private readonly IVisual _blur; private readonly IVisual _visual; private PixelSize _size; @@ -25,7 +24,7 @@ internal class WinUiCompositedWindow : IDisposable lock (_shared.SyncRoot) { _compositionRoundedRectangleGeometry?.Dispose(); - _blur?.Dispose(); + _blur.Dispose(); _mica?.Dispose(); _visual.Dispose(); _surfaceBrush.Dispose(); diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs index 9ee024f9ad..95ef338f08 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs @@ -1,15 +1,8 @@ using System; -using System.Runtime.InteropServices; -using Avalonia.MicroCom; -using Avalonia.OpenGL; -using Avalonia.OpenGL.Angle; using Avalonia.OpenGL.Egl; -using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; -using Avalonia.Utilities; using Avalonia.Win32.DirectX; using Avalonia.Win32.Interop; -using Avalonia.Win32.OpenGl.Angle; using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition @@ -18,7 +11,7 @@ namespace Avalonia.Win32.WinRT.Composition { private readonly WinUiCompositionShared _shared; private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; - private WinUiCompositedWindow _window; + private WinUiCompositedWindow? _window; private BlurEffect _blurEffect; public WinUiCompositedWindowSurface(WinUiCompositionShared shared, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) @@ -52,6 +45,8 @@ namespace Avalonia.Win32.WinRT.Composition internal class WinUiCompositedWindowRenderTarget : IDirect3D11TextureRenderTarget { + private static readonly Guid IID_ID3D11Texture2D = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); + private readonly IPlatformGraphicsContext _context; private readonly WinUiCompositedWindow _window; private readonly IUnknown _d3dDevice; @@ -63,6 +58,7 @@ namespace Avalonia.Win32.WinRT.Composition private PixelSize _size; private bool _lost; private readonly ICompositionDrawingSurfaceInterop _surfaceInterop; + private readonly ICompositionDrawingSurface _drawingSurface; public WinUiCompositedWindowRenderTarget(IPlatformGraphicsContext context, WinUiCompositedWindow window, IntPtr device, @@ -83,28 +79,33 @@ namespace Avalonia.Win32.WinRT.Composition _surface = _drawingSurface.QueryInterface(); _surfaceInterop = _drawingSurface.QueryInterface(); } - finally + catch { - if (_surfaceInterop == null) - Dispose(); + _surface?.Dispose(); + _surfaceInterop?.Dispose(); + _drawingSurface?.Dispose(); + _compositionDevice2?.Dispose(); + _compositionDevice?.Dispose(); + _interop?.Dispose(); + _compositor?.Dispose(); + _d3dDevice?.Dispose(); + throw; } } public void Dispose() { - _surface?.Dispose(); - _surfaceInterop?.Dispose(); - _drawingSurface?.Dispose(); - _compositionDevice2?.Dispose(); - _compositionDevice?.Dispose(); - _interop?.Dispose(); - _compositor?.Dispose(); - _d3dDevice?.Dispose(); + _surface.Dispose(); + _surfaceInterop.Dispose(); + _drawingSurface.Dispose(); + _compositionDevice2.Dispose(); + _compositionDevice.Dispose(); + _interop.Dispose(); + _compositor.Dispose(); + _d3dDevice.Dispose(); } public bool IsCorrupted => _context.IsLost || _lost; - private static Guid IID_ID3D11Texture2D = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); - private readonly ICompositionDrawingSurface _drawingSurface; public unsafe IDirect3D11TextureRenderTargetRenderSession BeginDraw() { @@ -155,12 +156,12 @@ namespace Avalonia.Win32.WinRT.Composition { if (needsEndDraw) _surfaceInterop.EndDraw(); - transaction?.Dispose(); + transaction.Dispose(); } } } - class Session : IDirect3D11TextureRenderTargetRenderSession + private class Session : IDirect3D11TextureRenderTargetRenderSession { private readonly IDisposable _transaction; private readonly PixelSize _size; @@ -200,4 +201,4 @@ namespace Avalonia.Win32.WinRT.Composition public double Scaling => _scaling; } } -} \ No newline at end of file +} diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs index 301e605e7a..602d0a3f8a 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs @@ -8,11 +8,11 @@ internal class WinUiCompositionShared : IDisposable public ICompositor Compositor { get; } public ICompositor5 Compositor5 { get; } public ICompositorDesktopInterop DesktopInterop { get; } - public ICompositionBrush BlurBrush; - public ICompositionBrush MicaBrush; + public ICompositionBrush BlurBrush { get; } + public ICompositionBrush? MicaBrush { get; } public object SyncRoot { get; } = new(); - public static readonly Version MinHostBackdropVersion = new Version(10, 0, 22000); + public static readonly Version MinHostBackdropVersion = new(10, 0, 22000); public WinUiCompositionShared(ICompositor compositor) { @@ -26,9 +26,9 @@ internal class WinUiCompositionShared : IDisposable public void Dispose() { BlurBrush.Dispose(); - MicaBrush.Dispose(); + MicaBrush?.Dispose(); DesktopInterop.Dispose(); Compositor.Dispose(); Compositor5.Dispose(); } -} \ No newline at end of file +} diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs index e7af4046d6..7b970868df 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs @@ -1,4 +1,3 @@ -using System; using System.Numerics; using MicroCom.Runtime; @@ -6,7 +5,7 @@ namespace Avalonia.Win32.WinRT.Composition; internal static class WinUiCompositionUtils { - public static ICompositionBrush CreateMicaBackdropBrush(ICompositor compositor) + public static ICompositionBrush? CreateMicaBackdropBrush(ICompositor compositor) { if (Win32Platform.WindowsVersion.Build < 22000) return null; @@ -18,7 +17,7 @@ internal static class WinUiCompositionUtils return blurredWallpaperBackdropBrush?.QueryInterface(); } - public static unsafe ICompositionBrush CreateAcrylicBlurBackdropBrush(ICompositor compositor) + public static ICompositionBrush CreateAcrylicBlurBackdropBrush(ICompositor compositor) { using var backDropParameterFactory = NativeWinRTMethods.CreateActivationFactory( @@ -39,7 +38,7 @@ internal static class WinUiCompositionUtils return compositionEffectBrush.QueryInterface(); } - public static ICompositionRoundedRectangleGeometry ClipVisual(ICompositor compositor, float? _backdropCornerRadius, params IVisual[] containerVisuals) + public static ICompositionRoundedRectangleGeometry? ClipVisual(ICompositor compositor, float? _backdropCornerRadius, params IVisual?[] containerVisuals) { if (!_backdropCornerRadius.HasValue) return null; @@ -77,7 +76,7 @@ internal static class WinUiCompositionUtils public static ICompositionBrush CreateBackdropBrush(ICompositor compositor) { - ICompositionBackdropBrush brush = null; + ICompositionBackdropBrush? brush = null; try { if (Win32Platform.WindowsVersion >= WinUiCompositionShared.MinHostBackdropVersion) @@ -98,4 +97,4 @@ internal static class WinUiCompositionUtils brush?.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs index a1408baae0..754af86c06 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs @@ -7,20 +7,17 @@ using Avalonia.Logging; using Avalonia.MicroCom; using Avalonia.OpenGL.Egl; using Avalonia.Rendering; -using Avalonia.Win32.DirectX; using Avalonia.Win32.Interop; -using Avalonia.Win32.OpenGl.Angle; -using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition; internal class WinUiCompositorConnection : IRenderTimer { private readonly WinUiCompositionShared _shared; - public event Action Tick; + public event Action? Tick; public bool RunsInBackground => true; - public unsafe WinUiCompositorConnection() + public WinUiCompositorConnection() { using var compositor = NativeWinRTMethods.CreateInstance("Windows.UI.Composition.Compositor"); /* @@ -42,10 +39,9 @@ internal class WinUiCompositorConnection : IRenderTimer _shared = new WinUiCompositionShared(compositor); } - static bool TryCreateAndRegisterCore() + private static bool TryCreateAndRegisterCore() { var tcs = new TaskCompletionSource(); - var pumpLock = new object(); var th = new Thread(() => { WinUiCompositorConnection connect; @@ -80,10 +76,10 @@ internal class WinUiCompositorConnection : IRenderTimer return tcs.Task.Result; } - class RunLoopHandler : CallbackBase, IAsyncActionCompletedHandler + private class RunLoopHandler : CallbackBase, IAsyncActionCompletedHandler { private readonly WinUiCompositorConnection _parent; - private Stopwatch _st = Stopwatch.StartNew(); + private readonly Stopwatch _st = Stopwatch.StartNew(); public RunLoopHandler(WinUiCompositorConnection parent) { @@ -101,7 +97,7 @@ internal class WinUiCompositorConnection : IRenderTimer private void RunLoop() { var cts = new CancellationTokenSource(); - AppDomain.CurrentDomain.ProcessExit += (sender, args) => + AppDomain.CurrentDomain.ProcessExit += (_, _) => cts.Cancel(); lock (_shared.SyncRoot) diff --git a/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs b/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs index 01b45f3df2..21f83addeb 100644 --- a/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs +++ b/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs @@ -1,12 +1,6 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Reflection; using System.Runtime.InteropServices; using System.Threading; -using Avalonia.MicroCom; -using Avalonia.Win32.Interop; using MicroCom.Runtime; namespace Avalonia.Win32.WinRT @@ -108,24 +102,24 @@ namespace Avalonia.Win32.WinRT [DllImport("combase.dll", PreserveSig = false)] private static extern IntPtr RoGetActivationFactory(IntPtr activatableClassId, ref Guid iid); - private static bool _initialized; + private static bool s_initialized; private static void EnsureRoInitialized() { - if (_initialized) + if (s_initialized) return; RoInitialize(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA ? RO_INIT_TYPE.RO_INIT_SINGLETHREADED : RO_INIT_TYPE.RO_INIT_MULTITHREADED); - _initialized = true; + s_initialized = true; } } - class HStringInterop : IDisposable + internal class HStringInterop : IDisposable { private IntPtr _s; - private bool _owns; + private readonly bool _owns; - public HStringInterop(string s) + public HStringInterop(string? s) { _s = s == null ? IntPtr.Zero : NativeWinRTMethods.WindowsCreateString(s); _owns = true; @@ -139,7 +133,7 @@ namespace Avalonia.Win32.WinRT public IntPtr Handle => _s; - public unsafe string Value + public unsafe string? Value { get { diff --git a/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs b/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs index 1fde57fe38..aabf148e75 100644 --- a/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs +++ b/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs @@ -2,12 +2,11 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using Avalonia.MicroCom; using MicroCom.Runtime; namespace Avalonia.Win32.WinRT { - class WinRTInspectable : IInspectable, IMicroComShadowContainer + internal class WinRTInspectable : IInspectable, IMicroComShadowContainer { public virtual void Dispose() { @@ -25,9 +24,9 @@ namespace Avalonia.Win32.WinRT *iidCount = (ulong) interfaces.Length; } - public IntPtr RuntimeClassName => NativeWinRTMethods.WindowsCreateString(GetType().FullName); + public IntPtr RuntimeClassName => NativeWinRTMethods.WindowsCreateString(GetType().FullName!); public TrustLevel TrustLevel => TrustLevel.BaseTrust; - public MicroComShadow Shadow { get; set; } + public MicroComShadow? Shadow { get; set; } public virtual void OnReferencedFromNative() { } diff --git a/src/Windows/Avalonia.Win32/WinScreen.cs b/src/Windows/Avalonia.Win32/WinScreen.cs index 1038f41a17..69d334cbcc 100644 --- a/src/Windows/Avalonia.Win32/WinScreen.cs +++ b/src/Windows/Avalonia.Win32/WinScreen.cs @@ -15,14 +15,16 @@ namespace Avalonia.Win32 public IntPtr Handle => _hMonitor; + /// public override int GetHashCode() { - return (int)_hMonitor; + return _hMonitor.GetHashCode(); } - public override bool Equals(object obj) + /// + public override bool Equals(object? obj) { - return (obj is WinScreen screen) ? _hMonitor == screen._hMonitor : base.Equals(obj); + return obj is WinScreen screen && _hMonitor == screen._hMonitor; } } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 8c362b0c29..288bdf2448 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -1,12 +1,9 @@ using System; -using System.Buffers; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using System.Text; using Avalonia.Automation.Peers; using Avalonia.Controls; -using Avalonia.Controls.Remote; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; @@ -27,9 +24,9 @@ namespace Avalonia.Win32 protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { const double wheelDelta = 120.0; - const long UiaRootObjectId = -25; + const long uiaRootObjectId = -25; uint timestamp = unchecked((uint)GetMessageTime()); - RawInputEventArgs e = null; + RawInputEventArgs? e = null; var shouldTakeFocus = false; var message = (WindowsMessage)msg; switch (message) @@ -92,7 +89,7 @@ namespace Avalonia.Win32 } // We need to release IMM context and state to avoid leaks. - if (Imm32InputMethod.Current.HWND == _hwnd) + if (Imm32InputMethod.Current.Hwnd == _hwnd) { Imm32InputMethod.Current.ClearLanguageAndWindow(); } @@ -104,7 +101,7 @@ namespace Avalonia.Win32 Closed?.Invoke(); _mouseDevice.Dispose(); - _touchDevice?.Dispose(); + _touchDevice.Dispose(); //Free other resources Dispose(); @@ -140,7 +137,7 @@ namespace Avalonia.Win32 { var key = KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)); - if (key != Key.None) + if (key != Key.None && _owner is not null) { e = new RawKeyEventArgs( WindowsKeyboardDevice.Instance, @@ -170,7 +167,7 @@ namespace Avalonia.Win32 { var key = KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)); - if (key != Key.None) + if (key != Key.None && _owner is not null) { e = new RawKeyEventArgs( WindowsKeyboardDevice.Instance, @@ -185,7 +182,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_CHAR: { // Ignore control chars and chars that were handled in WM_KEYDOWN. - if (ToInt32(wParam) >= 32 && !_ignoreWmChar) + if (ToInt32(wParam) >= 32 && !_ignoreWmChar && _owner is not null) { e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, _owner, new string((char)ToInt32(wParam), 1)); @@ -204,7 +201,7 @@ namespace Avalonia.Win32 break; } shouldTakeFocus = ShouldTakeFocusOnClick; - if (ShouldIgnoreTouchEmulatedMessage()) + if (ShouldIgnoreTouchEmulatedMessage() || _owner is null) { break; } @@ -213,7 +210,9 @@ namespace Avalonia.Win32 _mouseDevice, timestamp, _owner, +#pragma warning disable CS8509 message switch +#pragma warning restore CS8509 { WindowsMessage.WM_LBUTTONDOWN => RawPointerEventType.LeftButtonDown, WindowsMessage.WM_RBUTTONDOWN => RawPointerEventType.RightButtonDown, @@ -221,7 +220,7 @@ namespace Avalonia.Win32 WindowsMessage.WM_XBUTTONDOWN => HighWord(ToInt32(wParam)) == 1 ? RawPointerEventType.XButton1Down : - RawPointerEventType.XButton2Down + RawPointerEventType.XButton2Down, }, DipFromLParam(lParam), GetMouseModifiers(wParam)); break; @@ -236,7 +235,7 @@ namespace Avalonia.Win32 { break; } - if (ShouldIgnoreTouchEmulatedMessage()) + if (ShouldIgnoreTouchEmulatedMessage() || _owner is null) { break; } @@ -245,7 +244,9 @@ namespace Avalonia.Win32 _mouseDevice, timestamp, _owner, +#pragma warning disable CS8509 message switch +#pragma warning restore CS8509 { WindowsMessage.WM_LBUTTONUP => RawPointerEventType.LeftButtonUp, WindowsMessage.WM_RBUTTONUP => RawPointerEventType.RightButtonUp, @@ -309,6 +310,11 @@ namespace Avalonia.Win32 var prevPoint = _lastWmMousePoint; _lastWmMousePoint = currPoint; + if (_owner is null) + { + break; + } + e = new RawPointerEventArgs( _mouseDevice, timestamp, @@ -317,7 +323,7 @@ namespace Avalonia.Win32 point, GetMouseModifiers(wParam)) { - IntermediatePoints = new Lazy>(() => CreateLazyIntermediatePoints(currPoint, prevPoint)) + IntermediatePoints = new Lazy?>(() => CreateIntermediatePoints(currPoint, prevPoint)) }; break; @@ -325,7 +331,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_MOUSEWHEEL: { - if (IsMouseInPointerEnabled) + if (IsMouseInPointerEnabled || _owner is null) { break; } @@ -341,7 +347,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_MOUSEHWHEEL: { - if (IsMouseInPointerEnabled) + if (IsMouseInPointerEnabled || _owner is null) { break; } @@ -362,6 +368,10 @@ namespace Avalonia.Win32 break; } _trackingMouse = false; + if (_owner is null) + { + break; + } e = new RawPointerEventArgs( _mouseDevice, timestamp, @@ -377,7 +387,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_NCMBUTTONDOWN: case WindowsMessage.WM_NCXBUTTONDOWN: { - if (IsMouseInPointerEnabled) + if (IsMouseInPointerEnabled || _owner is null) { break; } @@ -385,7 +395,9 @@ namespace Avalonia.Win32 _mouseDevice, timestamp, _owner, +#pragma warning disable CS8509 message switch +#pragma warning restore CS8509 { WindowsMessage.WM_NCLBUTTONDOWN => RawPointerEventType .NonClientLeftButtonDown, @@ -396,12 +408,12 @@ namespace Avalonia.Win32 RawPointerEventType.XButton1Down : RawPointerEventType.XButton2Down, }, - PointToClient(WindowImpl.PointFromLParam(lParam)), GetMouseModifiers(wParam)); + PointToClient(PointFromLParam(lParam)), GetMouseModifiers(wParam)); break; } case WindowsMessage.WM_TOUCH: { - if (_wmPointerEnabled) + if (_wmPointerEnabled || _owner is null || Input is not { } input) { break; } @@ -414,7 +426,7 @@ namespace Avalonia.Win32 { foreach (var touchInput in touchInputs) { - Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time, + input.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time, _owner, touchInput.Flags.HasAllFlags(TouchInputFlags.TOUCHEVENTF_UP) ? RawPointerEventType.TouchEnd : @@ -438,14 +450,14 @@ namespace Avalonia.Win32 case WindowsMessage.WM_POINTERUP: case WindowsMessage.WM_POINTERUPDATE: { - if (!_wmPointerEnabled) + if (!_wmPointerEnabled || _owner is null) { break; } GetDevicePointerInfo(wParam, out var device, out var info, out var point, out var modifiers, ref timestamp); var eventType = GetEventType(message, info); - var args = CreatePointerArgs(device, timestamp, eventType, point, modifiers, info.pointerId); + var args = CreatePointerArgs(device, timestamp, _owner, eventType, point, modifiers, info.pointerId); args.IntermediatePoints = CreateLazyIntermediatePoints(info); e = args; break; @@ -454,19 +466,19 @@ namespace Avalonia.Win32 case WindowsMessage.WM_POINTERLEAVE: case WindowsMessage.WM_POINTERCAPTURECHANGED: { - if (!_wmPointerEnabled) + if (!_wmPointerEnabled || _owner is null) { break; } GetDevicePointerInfo(wParam, out var device, out var info, out var point, out var modifiers, ref timestamp); var eventType = device is TouchDevice ? RawPointerEventType.TouchCancel : RawPointerEventType.LeaveWindow; - e = CreatePointerArgs(device, timestamp, eventType, point, modifiers, info.pointerId); + e = CreatePointerArgs(device, timestamp, _owner, eventType, point, modifiers, info.pointerId); break; } case WindowsMessage.WM_POINTERWHEEL: case WindowsMessage.WM_POINTERHWHEEL: { - if (!_wmPointerEnabled) + if (!_wmPointerEnabled || _owner is null) { break; } @@ -488,7 +500,7 @@ namespace Avalonia.Win32 } // Do not generate events, but release mouse capture on any other device input. - GetDevicePointerInfo(wParam, out var device, out var info, out var point, out var modifiers, ref timestamp); + GetDevicePointerInfo(wParam, out var device, out _, out _, out _, ref timestamp); if (device != _mouseDevice) { _mouseDevice.Capture(null); @@ -726,9 +738,9 @@ namespace Avalonia.Win32 break; case WindowsMessage.WM_GETOBJECT: - if ((long)lParam == UiaRootObjectId && UiaCoreTypesApi.IsNetComInteropAvailable) + if ((long)lParam == uiaRootObjectId && UiaCoreTypesApi.IsNetComInteropAvailable && _owner is Control control) { - var peer = ControlAutomationPeer.CreatePeerForElement((Control)_owner); + var peer = ControlAutomationPeer.CreatePeerForElement(control); var node = AutomationNode.GetOrCreate(peer); return UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, wParam, lParam, node); } @@ -775,12 +787,12 @@ namespace Avalonia.Win32 } } - private unsafe Lazy> CreateLazyIntermediatePoints(POINTER_INFO info) + private Lazy?>? CreateLazyIntermediatePoints(POINTER_INFO info) { var historyCount = Math.Min((int)info.historyCount, MaxPointerHistorySize); if (historyCount > 1) { - return new Lazy>(() => + return new Lazy?>(() => { s_intermediatePointsPooledList.Clear(); s_intermediatePointsPooledList.Capacity = historyCount; @@ -829,7 +841,7 @@ namespace Avalonia.Win32 return null; } - private unsafe IReadOnlyList CreateLazyIntermediatePoints(MOUSEMOVEPOINT movePoint, MOUSEMOVEPOINT prevMovePoint) + private unsafe IReadOnlyList CreateIntermediatePoints(MOUSEMOVEPOINT movePoint, MOUSEMOVEPOINT prevMovePoint) { // To understand some of this code, please check MS docs: // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmousemovepointsex#remarks @@ -888,11 +900,11 @@ namespace Avalonia.Win32 } } - private RawPointerEventArgs CreatePointerArgs(IInputDevice device, ulong timestamp, RawPointerEventType eventType, RawPointerPoint point, RawInputModifiers modifiers, uint rawPointerId) + private RawPointerEventArgs CreatePointerArgs(IInputDevice device, ulong timestamp, IInputRoot owner, RawPointerEventType eventType, RawPointerPoint point, RawInputModifiers modifiers, uint rawPointerId) { return device is TouchDevice - ? new RawTouchEventArgs(device, timestamp, _owner, eventType, point, modifiers, rawPointerId) - : new RawPointerEventArgs(device, timestamp, _owner, eventType, point, modifiers) + ? new RawTouchEventArgs(device, timestamp, owner, eventType, point, modifiers, rawPointerId) + : new RawPointerEventArgs(device, timestamp, owner, eventType, point, modifiers) { RawPointerId = rawPointerId }; @@ -1029,7 +1041,7 @@ namespace Avalonia.Win32 // note: for non-ime language, also create it so that emoji panel tracks cursor var langid = LGID(hkl); - if (langid == _langid && Imm32InputMethod.Current.HWND == Hwnd) + if (langid == _langid && Imm32InputMethod.Current.Hwnd == Hwnd) { return; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index fb27ab7856..20ab738a2a 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -3,8 +3,6 @@ using Avalonia.Controls; using Avalonia.Input; using static Avalonia.Win32.Interop.UnmanagedMethods; -#nullable enable - namespace Avalonia.Win32 { public partial class WindowImpl @@ -13,7 +11,7 @@ namespace Avalonia.Win32 private HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam) { // Get the point coordinates for the hit test (screen space). - var ptMouse = WindowImpl.PointFromLParam(lParam); + var ptMouse = PointFromLParam(lParam); // Get the window rectangle. GetWindowRect(hWnd, out var rcWindow); @@ -101,11 +99,9 @@ namespace Avalonia.Win32 lRet = (IntPtr)hittestResult; - uint timestamp = unchecked((uint)GetMessageTime()); - if (hittestResult == HitTestValues.HTCAPTION) { - var position = PointToClient(WindowImpl.PointFromLParam(lParam)); + var position = PointToClient(PointFromLParam(lParam)); if (_owner is Window window) { diff --git a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs index 07f2311be8..49197a83e8 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs @@ -2,14 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using Avalonia.Controls; using Avalonia.Controls.Platform; -using Avalonia.Input; -using Avalonia.Input.Raw; -using Avalonia.Win32.Input; -using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32 { diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 1e0d92d442..57407b5f34 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1,33 +1,29 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; -using Avalonia.Controls; -using Avalonia.Automation.Peers; +using Avalonia.Collections.Pooled; using Avalonia.Controls.Platform; -using Avalonia.Input; +using Avalonia.Controls; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; -using Avalonia.OpenGL; -using Avalonia.OpenGL.Angle; +using Avalonia.Input; +using Avalonia.Metadata; using Avalonia.OpenGL.Egl; -using Avalonia.OpenGL.Surfaces; +using Avalonia.Platform.Storage; using Avalonia.Platform; -using Avalonia.Rendering; using Avalonia.Rendering.Composition; -using Avalonia.Win32.Automation; +using Avalonia.Rendering; +using Avalonia.Win32.DirectX; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; +using Avalonia.Win32.OpenGl.Angle; using Avalonia.Win32.OpenGl; -using Avalonia.Win32.WinRT; using Avalonia.Win32.WinRT.Composition; +using Avalonia.Win32.WinRT; using static Avalonia.Win32.Interop.UnmanagedMethods; -using Avalonia.Collections.Pooled; -using Avalonia.Metadata; -using Avalonia.Platform.Storage; -using Avalonia.Win32.DirectX; -using Avalonia.Win32.OpenGl.Angle; namespace Avalonia.Win32 { @@ -37,13 +33,13 @@ namespace Avalonia.Win32 [Unstable] public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo { - private static readonly List s_instances = new List(); + private static readonly List s_instances = new(); - private static readonly IntPtr DefaultCursor = LoadCursor( + private static readonly IntPtr s_defaultCursor = LoadCursor( IntPtr.Zero, new IntPtr((int)UnmanagedMethods.Cursor.IDC_ARROW)); private static readonly Dictionary s_edgeLookup = - new Dictionary + new() { { WindowEdge.East, HitTestValues.HTRIGHT }, { WindowEdge.North, HitTestValues.HTTOP }, @@ -61,8 +57,8 @@ namespace Avalonia.Win32 private Thickness _extendedMargins; private Thickness _offScreenMargin; private double _extendTitleBarHint = -1; - private bool _isUsingComposition; - private IBlurHost _blurHost; + private readonly bool _isUsingComposition; + private readonly IBlurHost? _blurHost; private PlatformResizeReason _resizeReason; private MOUSEMOVEPOINT _lastWmMousePoint; @@ -76,26 +72,26 @@ namespace Avalonia.Win32 private readonly PenDevice _penDevice; private readonly ManagedDeferredRendererLock _rendererLock; private readonly FramebufferManager _framebuffer; - private readonly object _gl; + private readonly object? _gl; private readonly bool _wmPointerEnabled; - private Win32NativeControlHost _nativeControlHost; - private IStorageProvider _storageProvider; + private readonly Win32NativeControlHost _nativeControlHost; + private readonly IStorageProvider _storageProvider; private WndProc _wndProcDelegate; - private string _className; + private string? _className; private IntPtr _hwnd; - private IInputRoot _owner; + private IInputRoot? _owner; private WindowProperties _windowProperties; private bool _trackingMouse;//ToDo - there is something missed. Needs investigation @Steven Kirk private bool _topmost; private double _scaling = 1; private WindowState _showWindowState; private WindowState _lastWindowState; - private OleDropTarget _dropTarget; + private OleDropTarget? _dropTarget; private Size _minSize; private Size _maxSize; private POINT _maxTrackSize; - private WindowImpl _parent; + private WindowImpl? _parent; private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default; private bool _isCloseRequested; private bool _shown; @@ -142,7 +138,7 @@ namespace Avalonia.Win32 angle.PlatformApi == AngleOptions.PlatformApi.DirectX11; _isUsingComposition = compositionConnector is { } && isUsingAngleDX11; - DxgiConnection dxgiConnection = null; + DxgiConnection? dxgiConnection = null; var isUsingDxgiSwapchain = false; if (!_isUsingComposition) { @@ -159,23 +155,20 @@ namespace Avalonia.Win32 { if (_isUsingComposition) { - var cgl = compositionConnector.CreateSurface(this); + var cgl = compositionConnector!.CreateSurface(this); _blurHost = cgl; - _gl = cgl; - - _isUsingComposition = true; } else if (isUsingDxgiSwapchain) { - var dxgigl = new DxgiSwapchainWindow(dxgiConnection, this); + var dxgigl = new DxgiSwapchainWindow(dxgiConnection!, this); _gl = dxgigl; } else { - if (glPlatform is AngleWin32PlatformGraphics egl2) + if (glPlatform is AngleWin32PlatformGraphics) _gl = new EglGlPlatformSurface(this); - else if (glPlatform is WglPlatformOpenGlInterface wgl) + else if (glPlatform is WglPlatformOpenGlInterface) _gl = new WglGlPlatformSurface(this); } } @@ -187,29 +180,41 @@ namespace Avalonia.Win32 s_instances.Add(this); } - public Action Activated { get; set; } + /// + public Action? Activated { get; set; } - public Func Closing { get; set; } + /// + public Func? Closing { get; set; } - public Action Closed { get; set; } + /// + public Action? Closed { get; set; } - public Action Deactivated { get; set; } + /// + public Action? Deactivated { get; set; } - public Action Input { get; set; } + /// + public Action? Input { get; set; } - public Action Paint { get; set; } + /// + public Action? Paint { get; set; } - public Action Resized { get; set; } + /// + public Action? Resized { get; set; } - public Action ScalingChanged { get; set; } + /// + public Action? ScalingChanged { get; set; } - public Action PositionChanged { get; set; } + /// + public Action? PositionChanged { get; set; } - public Action WindowStateChanged { get; set; } + /// + public Action? WindowStateChanged { get; set; } - public Action LostFocus { get; set; } + /// + public Action? LostFocus { get; set; } - public Action TransparencyLevelChanged { get; set; } + /// + public Action? TransparencyLevelChanged { get; set; } public Thickness BorderThickness { @@ -240,10 +245,13 @@ namespace Avalonia.Win32 private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.Scaling ?? 1; + /// public double RenderScaling => _scaling; + /// public double DesktopScaling => RenderScaling; + /// public Size ClientSize { get @@ -254,6 +262,7 @@ namespace Avalonia.Win32 } } + /// public Size? FrameSize { get @@ -269,14 +278,18 @@ namespace Avalonia.Win32 } } + /// public IScreenImpl Screen { get; } + /// public IPlatformHandle Handle { get; private set; } + /// public virtual Size MaxAutoSizeHint => new Size(_maxTrackSize.X / RenderScaling, _maxTrackSize.Y / RenderScaling); public IMouseDevice MouseDevice => _mouseDevice; + /// public WindowState WindowState { get @@ -314,13 +327,15 @@ namespace Avalonia.Win32 } } + /// public WindowTransparencyLevel TransparencyLevel { get; private set; } protected IntPtr Hwnd => _hwnd; private bool IsMouseInPointerEnabled => _wmPointerEnabled && IsMouseInPointerEnabled(); - public object TryGetFeature(Type featureType) + /// + public object? TryGetFeature(Type featureType) { if (featureType == typeof(ITextInputMethodImpl)) { @@ -339,7 +354,8 @@ namespace Avalonia.Win32 return null; } - + + /// public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { TransparencyLevel = EnableBlur(transparencyLevel); @@ -521,8 +537,13 @@ namespace Avalonia.Win32 } } - public IEnumerable Surfaces => new object[] { (IPlatformNativeSurfaceHandle)Handle, _gl, _framebuffer }; + /// + public IEnumerable Surfaces + => _gl is null ? + new object[] { Handle, _framebuffer } : + new object[] { Handle, _gl, _framebuffer }; + /// public PixelPoint Position { get @@ -568,17 +589,21 @@ namespace Avalonia.Win32 } } + /// public void Move(PixelPoint point) => Position = point; + /// public void SetMinMaxSize(Size minSize, Size maxSize) { _minSize = minSize; _maxSize = maxSize; } + /// public IRenderer CreateRenderer(IRenderRoot root) => new CompositingRenderer(root, Win32Platform.Compositor, () => Surfaces); + /// public void Resize(Size value, PlatformResizeReason reason) { if (WindowState != WindowState.Normal) @@ -606,13 +631,16 @@ namespace Avalonia.Win32 } } + /// public void Activate() { SetForegroundWindow(_hwnd); } - public IPopupImpl CreatePopup() => Win32Platform.UseOverlayPopups ? null : new PopupImpl(this); + /// + public IPopupImpl? CreatePopup() => Win32Platform.UseOverlayPopups ? null : new PopupImpl(this); + /// public void Dispose() { (_gl as IDisposable)?.Dispose(); @@ -654,6 +682,7 @@ namespace Avalonia.Win32 InvalidateRect(_hwnd, ref r, false); } + /// public Point PointToClient(PixelPoint point) { var p = new POINT { X = point.X, Y = point.Y }; @@ -661,6 +690,7 @@ namespace Avalonia.Win32 return new Point(p.X, p.Y) / RenderScaling; } + /// public PixelPoint PointToScreen(Point point) { point *= RenderScaling; @@ -669,29 +699,34 @@ namespace Avalonia.Win32 return new PixelPoint(p.X, p.Y); } + /// public void SetInputRoot(IInputRoot inputRoot) { _owner = inputRoot; - CreateDropTarget(); + CreateDropTarget(inputRoot); } + /// public void Hide() { UnmanagedMethods.ShowWindow(_hwnd, ShowWindowCommand.Hide); _shown = false; } + /// public virtual void Show(bool activate, bool isDialog) { SetParent(_parent); ShowWindow(_showWindowState, activate); } - public Action GotInputWhenDisabled { get; set; } + /// + public Action? GotInputWhenDisabled { get; set; } - public void SetParent(IWindowImpl parent) + /// + public void SetParent(IWindowImpl? parent) { - _parent = (WindowImpl)parent; + _parent = parent as WindowImpl; var parentHwnd = _parent?._hwnd ?? IntPtr.Zero; @@ -704,8 +739,10 @@ namespace Avalonia.Win32 SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, parentHwnd); } + /// public void SetEnabled(bool enable) => EnableWindow(_hwnd, enable); + /// public void BeginMoveDrag(PointerPressedEventArgs e) { e.Pointer.Capture(null); @@ -713,6 +750,7 @@ namespace Avalonia.Win32 new IntPtr((int)HitTestValues.HTCAPTION), IntPtr.Zero); } + /// public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) { if (_windowProperties.IsResizable) @@ -727,35 +765,37 @@ namespace Avalonia.Win32 } } - public void SetTitle(string title) + /// + public void SetTitle(string? title) { SetWindowText(_hwnd, title); } - public void SetCursor(ICursorImpl cursor) + /// + public void SetCursor(ICursorImpl? cursor) { var impl = cursor as CursorImpl; - if (cursor is null || impl is object) - { - var hCursor = impl?.Handle ?? DefaultCursor; - SetClassLong(_hwnd, ClassLongIndex.GCLP_HCURSOR, hCursor); + var hCursor = impl?.Handle ?? s_defaultCursor; + SetClassLong(_hwnd, ClassLongIndex.GCLP_HCURSOR, hCursor); - if (_owner.IsPointerOver) - { - UnmanagedMethods.SetCursor(hCursor); - } + if (_owner?.IsPointerOver == true) + { + UnmanagedMethods.SetCursor(hCursor); } } - public void SetIcon(IWindowIconImpl icon) + /// + public void SetIcon(IWindowIconImpl? icon) { - var impl = (IconImpl)icon; + var impl = icon as IconImpl; + var hIcon = impl?.HIcon ?? IntPtr.Zero; PostMessage(_hwnd, (int)WindowsMessage.WM_SETICON, new IntPtr((int)Icons.ICON_BIG), hIcon); } + /// public void ShowTaskbarIcon(bool value) { var newWindowProperties = _windowProperties; @@ -765,6 +805,7 @@ namespace Avalonia.Win32 UpdateWindowProperties(newWindowProperties); } + /// public void CanResize(bool value) { var newWindowProperties = _windowProperties; @@ -774,6 +815,7 @@ namespace Avalonia.Win32 UpdateWindowProperties(newWindowProperties); } + /// public void SetSystemDecorations(SystemDecorations value) { var newWindowProperties = _windowProperties; @@ -783,6 +825,7 @@ namespace Avalonia.Win32 UpdateWindowProperties(newWindowProperties); } + /// public void SetTopmost(bool value) { if (value == _topmost) @@ -799,6 +842,7 @@ namespace Avalonia.Win32 _topmost = value; } + /// public unsafe void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { if (Win32Platform.WindowsVersion.Build >= 22000) @@ -829,6 +873,9 @@ namespace Avalonia.Win32 IntPtr.Zero); } + [MemberNotNull(nameof(_wndProcDelegate))] + [MemberNotNull(nameof(_className))] + [MemberNotNull(nameof(Handle))] private void CreateWindow() { // Ensure that the delegate doesn't get garbage collected by storing it as a field. @@ -845,7 +892,7 @@ namespace Avalonia.Win32 style = (int)windowClassStyle, lpfnWndProc = _wndProcDelegate, hInstance = GetModuleHandle(null), - hCursor = DefaultCursor, + hCursor = s_defaultCursor, hbrBackground = IntPtr.Zero, lpszClassName = _className }; @@ -878,20 +925,23 @@ namespace Avalonia.Win32 monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpix, - out var dpiy) == 0) + out _) == 0) { _scaling = dpix / 96.0; } } } - private void CreateDropTarget() + private void CreateDropTarget(IInputRoot inputRoot) { - var odt = new OleDropTarget(this, _owner); - - if (OleContext.Current?.RegisterDragDrop(Handle, odt) ?? false) + if (AvaloniaLocator.Current.GetService() is { } dragDropDevice) { - _dropTarget = odt; + var odt = new OleDropTarget(this, inputRoot, dragDropDevice); + + if (OleContext.Current?.RegisterDragDrop(Handle, odt) ?? false) + { + _dropTarget = odt; + } } } @@ -1105,7 +1155,7 @@ namespace Avalonia.Win32 case WindowState.FullScreen: newWindowProperties.IsFullScreen = true; - command = IsWindowVisible(_hwnd) ? (ShowWindowCommand?)null : ShowWindowCommand.Restore; + command = IsWindowVisible(_hwnd) ? null : ShowWindowCommand.Restore; break; default: @@ -1370,13 +1420,13 @@ namespace Avalonia.Win32 private const int MF_DISABLED = 0x2; private const int SC_CLOSE = 0xF060; - static void DisableCloseButton(IntPtr hwnd) + private static void DisableCloseButton(IntPtr hwnd) { EnableMenuItem(GetSystemMenu(hwnd, false), SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED); } - static void EnableCloseButton(IntPtr hwnd) + private static void EnableCloseButton(IntPtr hwnd) { EnableMenuItem(GetSystemMenu(hwnd, false), SC_CLOSE, MF_BYCOMMAND | MF_ENABLED); @@ -1407,6 +1457,7 @@ namespace Avalonia.Win32 IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; + /// public void SetExtendClientAreaToDecorationsHint(bool hint) { _isClientAreaExtended = hint; @@ -1414,6 +1465,7 @@ namespace Avalonia.Win32 ExtendClientArea(); } + /// public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) { _extendChromeHints = hints; @@ -1433,7 +1485,7 @@ namespace Avalonia.Win32 public bool IsClientAreaExtendedToDecorations => _isClientAreaExtended; /// - public Action ExtendClientAreaToDecorationsChanged { get; set; } + public Action? ExtendClientAreaToDecorationsChanged { get; set; } /// public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.PreferSystemChrome); diff --git a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs index 3244e75a34..f2da804107 100644 --- a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs +++ b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs @@ -2,7 +2,6 @@ using System; using System.Collections.ObjectModel; using System.IO; using System.Linq; -using Avalonia.Reactive; using Avalonia.Controls.Platform; using Avalonia.Logging; using Avalonia.Threading; @@ -12,12 +11,12 @@ namespace Avalonia.Win32 internal class WindowsMountedVolumeInfoListener : IDisposable { private readonly IDisposable _disposable; - private bool _beenDisposed = false; - private ObservableCollection mountedDrives; + private bool _beenDisposed; + private readonly ObservableCollection _mountedDrives; public WindowsMountedVolumeInfoListener(ObservableCollection mountedDrives) { - this.mountedDrives = mountedDrives; + _mountedDrives = mountedDrives; _disposable = DispatcherTimer.Run(Poll, TimeSpan.FromSeconds(1)); @@ -51,14 +50,14 @@ namespace Avalonia.Win32 }) .ToArray(); - if (mountedDrives.SequenceEqual(mountVolInfos)) + if (_mountedDrives.SequenceEqual(mountVolInfos)) return true; else { - mountedDrives.Clear(); + _mountedDrives.Clear(); foreach (var i in mountVolInfos) - mountedDrives.Add(i); + _mountedDrives.Add(i); return true; } } diff --git a/src/tools/DevGenerators/Helpers.cs b/src/tools/DevGenerators/Helpers.cs index 3da89d2d0e..9cebf6daea 100644 --- a/src/tools/DevGenerators/Helpers.cs +++ b/src/tools/DevGenerators/Helpers.cs @@ -6,16 +6,21 @@ namespace Generator; static class Helpers { + private static readonly SymbolDisplayFormat s_symbolDisplayFormat = + SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions( + SymbolDisplayFormat.FullyQualifiedFormat.MiscellaneousOptions | + SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); + public static StringBuilder Pad(this StringBuilder sb, int count) => sb.Append(' ', count * 4); public static string GetFullyQualifiedName(this ISymbol symbol) { - return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + return symbol.ToDisplayString(s_symbolDisplayFormat); } public static bool HasFullyQualifiedName(this ISymbol symbol, string name) { - return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == name; + return symbol.ToDisplayString(s_symbolDisplayFormat) == name; } public static bool HasAttributeWithFullyQualifiedName(this ISymbol symbol, string name) @@ -28,4 +33,4 @@ static class Helpers return false; } -} \ No newline at end of file +} From 2b08668c644225edb78f841c7ac529704dab4623 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Sun, 12 Feb 2023 12:14:05 +0100 Subject: [PATCH 22/61] Addressed feedback on PR #10307 --- .../Avalonia.Win32/NonPumpingSyncContext.cs | 43 ++++++++++-- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 67 ++++++++----------- src/Windows/Avalonia.Win32/WindowImpl.cs | 57 ++-------------- 3 files changed, 71 insertions(+), 96 deletions(-) diff --git a/src/Windows/Avalonia.Win32/NonPumpingSyncContext.cs b/src/Windows/Avalonia.Win32/NonPumpingSyncContext.cs index 0c02f643b2..2c4d2c9468 100644 --- a/src/Windows/Avalonia.Win32/NonPumpingSyncContext.cs +++ b/src/Windows/Avalonia.Win32/NonPumpingSyncContext.cs @@ -8,17 +8,42 @@ namespace Avalonia.Win32 { internal class NonPumpingSyncContext : SynchronizationContext, IDisposable { - private readonly SynchronizationContext _inner; + private readonly SynchronizationContext? _inner; - private NonPumpingSyncContext(SynchronizationContext inner) + private NonPumpingSyncContext(SynchronizationContext? inner) { _inner = inner; SetWaitNotificationRequired(); SetSynchronizationContext(this); } - public override void Post(SendOrPostCallback d, object? state) => _inner.Post(d, state); - public override void Send(SendOrPostCallback d, object? state) => _inner.Send(d, state); + public override void Post(SendOrPostCallback d, object? state) + { + if (_inner is null) + { +#if NET6_0_OR_GREATER + ThreadPool.QueueUserWorkItem(static x => x.d(x.state), (d, state), false); +#else + ThreadPool.QueueUserWorkItem(_ => d(state)); +#endif + } + else + { + _inner.Post(d, state); + } + } + + public override void Send(SendOrPostCallback d, object? state) + { + if (_inner is null) + { + d(state); + } + else + { + _inner.Send(d, state); + } + } #if !NET6_0_OR_GREATER [PrePrepareMethod] @@ -34,7 +59,15 @@ namespace Avalonia.Win32 public static IDisposable? Use() { var current = Current; - return current is null or NonPumpingSyncContext ? null : new NonPumpingSyncContext(current); + if (current == null) + { + if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) + return null; + } + if (current is NonPumpingSyncContext) + return null; + + return new NonPumpingSyncContext(current); } internal class HelperImpl : NonPumpingLockHelper.IHelperImpl diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 288bdf2448..ed80f88b53 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -137,12 +137,12 @@ namespace Avalonia.Win32 { var key = KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)); - if (key != Key.None && _owner is not null) + if (key != Key.None) { e = new RawKeyEventArgs( WindowsKeyboardDevice.Instance, timestamp, - _owner, + Owner, RawKeyEventType.KeyDown, key, WindowsKeyboardDevice.Instance.Modifiers); @@ -167,12 +167,12 @@ namespace Avalonia.Win32 { var key = KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)); - if (key != Key.None && _owner is not null) + if (key != Key.None) { e = new RawKeyEventArgs( WindowsKeyboardDevice.Instance, timestamp, - _owner, + Owner, RawKeyEventType.KeyUp, key, WindowsKeyboardDevice.Instance.Modifiers); @@ -182,9 +182,9 @@ namespace Avalonia.Win32 case WindowsMessage.WM_CHAR: { // Ignore control chars and chars that were handled in WM_KEYDOWN. - if (ToInt32(wParam) >= 32 && !_ignoreWmChar && _owner is not null) + if (ToInt32(wParam) >= 32 && !_ignoreWmChar) { - e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, _owner, + e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, Owner, new string((char)ToInt32(wParam), 1)); } @@ -201,7 +201,7 @@ namespace Avalonia.Win32 break; } shouldTakeFocus = ShouldTakeFocusOnClick; - if (ShouldIgnoreTouchEmulatedMessage() || _owner is null) + if (ShouldIgnoreTouchEmulatedMessage()) { break; } @@ -209,7 +209,7 @@ namespace Avalonia.Win32 e = new RawPointerEventArgs( _mouseDevice, timestamp, - _owner, + Owner, #pragma warning disable CS8509 message switch #pragma warning restore CS8509 @@ -235,7 +235,7 @@ namespace Avalonia.Win32 { break; } - if (ShouldIgnoreTouchEmulatedMessage() || _owner is null) + if (ShouldIgnoreTouchEmulatedMessage()) { break; } @@ -243,7 +243,7 @@ namespace Avalonia.Win32 e = new RawPointerEventArgs( _mouseDevice, timestamp, - _owner, + Owner, #pragma warning disable CS8509 message switch #pragma warning restore CS8509 @@ -310,15 +310,10 @@ namespace Avalonia.Win32 var prevPoint = _lastWmMousePoint; _lastWmMousePoint = currPoint; - if (_owner is null) - { - break; - } - e = new RawPointerEventArgs( _mouseDevice, timestamp, - _owner, + Owner, RawPointerEventType.Move, point, GetMouseModifiers(wParam)) @@ -331,14 +326,14 @@ namespace Avalonia.Win32 case WindowsMessage.WM_MOUSEWHEEL: { - if (IsMouseInPointerEnabled || _owner is null) + if (IsMouseInPointerEnabled) { break; } e = new RawMouseWheelEventArgs( _mouseDevice, timestamp, - _owner, + Owner, PointToClient(PointFromLParam(lParam)), new Vector(0, (ToInt32(wParam) >> 16) / wheelDelta), GetMouseModifiers(wParam)); @@ -347,14 +342,14 @@ namespace Avalonia.Win32 case WindowsMessage.WM_MOUSEHWHEEL: { - if (IsMouseInPointerEnabled || _owner is null) + if (IsMouseInPointerEnabled) { break; } e = new RawMouseWheelEventArgs( _mouseDevice, timestamp, - _owner, + Owner, PointToClient(PointFromLParam(lParam)), new Vector(-(ToInt32(wParam) >> 16) / wheelDelta, 0), GetMouseModifiers(wParam)); @@ -368,14 +363,10 @@ namespace Avalonia.Win32 break; } _trackingMouse = false; - if (_owner is null) - { - break; - } e = new RawPointerEventArgs( _mouseDevice, timestamp, - _owner, + Owner, RawPointerEventType.LeaveWindow, new Point(-1, -1), WindowsKeyboardDevice.Instance.Modifiers); @@ -387,14 +378,14 @@ namespace Avalonia.Win32 case WindowsMessage.WM_NCMBUTTONDOWN: case WindowsMessage.WM_NCXBUTTONDOWN: { - if (IsMouseInPointerEnabled || _owner is null) + if (IsMouseInPointerEnabled) { break; } e = new RawPointerEventArgs( _mouseDevice, timestamp, - _owner, + Owner, #pragma warning disable CS8509 message switch #pragma warning restore CS8509 @@ -413,7 +404,7 @@ namespace Avalonia.Win32 } case WindowsMessage.WM_TOUCH: { - if (_wmPointerEnabled || _owner is null || Input is not { } input) + if (_wmPointerEnabled || Input is not { } input) { break; } @@ -427,7 +418,7 @@ namespace Avalonia.Win32 foreach (var touchInput in touchInputs) { input.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time, - _owner, + Owner, touchInput.Flags.HasAllFlags(TouchInputFlags.TOUCHEVENTF_UP) ? RawPointerEventType.TouchEnd : touchInput.Flags.HasAllFlags(TouchInputFlags.TOUCHEVENTF_DOWN) ? @@ -450,14 +441,14 @@ namespace Avalonia.Win32 case WindowsMessage.WM_POINTERUP: case WindowsMessage.WM_POINTERUPDATE: { - if (!_wmPointerEnabled || _owner is null) + if (!_wmPointerEnabled) { break; } GetDevicePointerInfo(wParam, out var device, out var info, out var point, out var modifiers, ref timestamp); var eventType = GetEventType(message, info); - var args = CreatePointerArgs(device, timestamp, _owner, eventType, point, modifiers, info.pointerId); + var args = CreatePointerArgs(device, timestamp, eventType, point, modifiers, info.pointerId); args.IntermediatePoints = CreateLazyIntermediatePoints(info); e = args; break; @@ -466,19 +457,19 @@ namespace Avalonia.Win32 case WindowsMessage.WM_POINTERLEAVE: case WindowsMessage.WM_POINTERCAPTURECHANGED: { - if (!_wmPointerEnabled || _owner is null) + if (!_wmPointerEnabled) { break; } GetDevicePointerInfo(wParam, out var device, out var info, out var point, out var modifiers, ref timestamp); var eventType = device is TouchDevice ? RawPointerEventType.TouchCancel : RawPointerEventType.LeaveWindow; - e = CreatePointerArgs(device, timestamp, _owner, eventType, point, modifiers, info.pointerId); + e = CreatePointerArgs(device, timestamp, eventType, point, modifiers, info.pointerId); break; } case WindowsMessage.WM_POINTERWHEEL: case WindowsMessage.WM_POINTERHWHEEL: { - if (!_wmPointerEnabled || _owner is null) + if (!_wmPointerEnabled) { break; } @@ -486,7 +477,7 @@ namespace Avalonia.Win32 var val = (ToInt32(wParam) >> 16) / wheelDelta; var delta = message == WindowsMessage.WM_POINTERWHEEL ? new Vector(0, val) : new Vector(val, 0); - e = new RawMouseWheelEventArgs(device, timestamp, _owner, point.Position, delta, modifiers) + e = new RawMouseWheelEventArgs(device, timestamp, Owner, point.Position, delta, modifiers) { RawPointerId = info.pointerId }; @@ -900,11 +891,11 @@ namespace Avalonia.Win32 } } - private RawPointerEventArgs CreatePointerArgs(IInputDevice device, ulong timestamp, IInputRoot owner, RawPointerEventType eventType, RawPointerPoint point, RawInputModifiers modifiers, uint rawPointerId) + private RawPointerEventArgs CreatePointerArgs(IInputDevice device, ulong timestamp, RawPointerEventType eventType, RawPointerPoint point, RawInputModifiers modifiers, uint rawPointerId) { return device is TouchDevice - ? new RawTouchEventArgs(device, timestamp, owner, eventType, point, modifiers, rawPointerId) - : new RawPointerEventArgs(device, timestamp, owner, eventType, point, modifiers) + ? new RawTouchEventArgs(device, timestamp, Owner, eventType, point, modifiers, rawPointerId) + : new RawPointerEventArgs(device, timestamp, Owner, eventType, point, modifiers) { RawPointerId = rawPointerId }; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 57407b5f34..812c72fd3c 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -180,40 +180,31 @@ namespace Avalonia.Win32 s_instances.Add(this); } - /// + private IInputRoot Owner + => _owner ?? throw new InvalidOperationException($"{nameof(SetInputRoot)} must have been called"); + public Action? Activated { get; set; } - /// public Func? Closing { get; set; } - /// public Action? Closed { get; set; } - /// public Action? Deactivated { get; set; } - /// public Action? Input { get; set; } - /// public Action? Paint { get; set; } - /// public Action? Resized { get; set; } - /// public Action? ScalingChanged { get; set; } - /// public Action? PositionChanged { get; set; } - /// public Action? WindowStateChanged { get; set; } - /// public Action? LostFocus { get; set; } - /// public Action? TransparencyLevelChanged { get; set; } public Thickness BorderThickness @@ -245,13 +236,10 @@ namespace Avalonia.Win32 private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.Scaling ?? 1; - /// public double RenderScaling => _scaling; - /// public double DesktopScaling => RenderScaling; - /// public Size ClientSize { get @@ -262,7 +250,6 @@ namespace Avalonia.Win32 } } - /// public Size? FrameSize { get @@ -278,18 +265,14 @@ namespace Avalonia.Win32 } } - /// public IScreenImpl Screen { get; } - /// public IPlatformHandle Handle { get; private set; } - /// public virtual Size MaxAutoSizeHint => new Size(_maxTrackSize.X / RenderScaling, _maxTrackSize.Y / RenderScaling); public IMouseDevice MouseDevice => _mouseDevice; - /// public WindowState WindowState { get @@ -327,14 +310,12 @@ namespace Avalonia.Win32 } } - /// public WindowTransparencyLevel TransparencyLevel { get; private set; } protected IntPtr Hwnd => _hwnd; private bool IsMouseInPointerEnabled => _wmPointerEnabled && IsMouseInPointerEnabled(); - /// public object? TryGetFeature(Type featureType) { if (featureType == typeof(ITextInputMethodImpl)) @@ -355,7 +336,6 @@ namespace Avalonia.Win32 return null; } - /// public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { TransparencyLevel = EnableBlur(transparencyLevel); @@ -537,13 +517,11 @@ namespace Avalonia.Win32 } } - /// public IEnumerable Surfaces => _gl is null ? new object[] { Handle, _framebuffer } : new object[] { Handle, _gl, _framebuffer }; - /// public PixelPoint Position { get @@ -589,21 +567,17 @@ namespace Avalonia.Win32 } } - /// public void Move(PixelPoint point) => Position = point; - /// public void SetMinMaxSize(Size minSize, Size maxSize) { _minSize = minSize; _maxSize = maxSize; } - /// public IRenderer CreateRenderer(IRenderRoot root) => new CompositingRenderer(root, Win32Platform.Compositor, () => Surfaces); - /// public void Resize(Size value, PlatformResizeReason reason) { if (WindowState != WindowState.Normal) @@ -631,16 +605,13 @@ namespace Avalonia.Win32 } } - /// public void Activate() { SetForegroundWindow(_hwnd); } - /// public IPopupImpl? CreatePopup() => Win32Platform.UseOverlayPopups ? null : new PopupImpl(this); - /// public void Dispose() { (_gl as IDisposable)?.Dispose(); @@ -682,7 +653,6 @@ namespace Avalonia.Win32 InvalidateRect(_hwnd, ref r, false); } - /// public Point PointToClient(PixelPoint point) { var p = new POINT { X = point.X, Y = point.Y }; @@ -690,7 +660,6 @@ namespace Avalonia.Win32 return new Point(p.X, p.Y) / RenderScaling; } - /// public PixelPoint PointToScreen(Point point) { point *= RenderScaling; @@ -699,31 +668,26 @@ namespace Avalonia.Win32 return new PixelPoint(p.X, p.Y); } - /// public void SetInputRoot(IInputRoot inputRoot) { _owner = inputRoot; CreateDropTarget(inputRoot); } - /// public void Hide() { UnmanagedMethods.ShowWindow(_hwnd, ShowWindowCommand.Hide); _shown = false; } - /// public virtual void Show(bool activate, bool isDialog) { SetParent(_parent); ShowWindow(_showWindowState, activate); } - /// public Action? GotInputWhenDisabled { get; set; } - /// public void SetParent(IWindowImpl? parent) { _parent = parent as WindowImpl; @@ -739,10 +703,8 @@ namespace Avalonia.Win32 SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, parentHwnd); } - /// public void SetEnabled(bool enable) => EnableWindow(_hwnd, enable); - /// public void BeginMoveDrag(PointerPressedEventArgs e) { e.Pointer.Capture(null); @@ -750,7 +712,6 @@ namespace Avalonia.Win32 new IntPtr((int)HitTestValues.HTCAPTION), IntPtr.Zero); } - /// public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) { if (_windowProperties.IsResizable) @@ -765,13 +726,11 @@ namespace Avalonia.Win32 } } - /// public void SetTitle(string? title) { SetWindowText(_hwnd, title); } - /// public void SetCursor(ICursorImpl? cursor) { var impl = cursor as CursorImpl; @@ -779,13 +738,12 @@ namespace Avalonia.Win32 var hCursor = impl?.Handle ?? s_defaultCursor; SetClassLong(_hwnd, ClassLongIndex.GCLP_HCURSOR, hCursor); - if (_owner?.IsPointerOver == true) + if (Owner.IsPointerOver) { UnmanagedMethods.SetCursor(hCursor); } } - /// public void SetIcon(IWindowIconImpl? icon) { var impl = icon as IconImpl; @@ -795,7 +753,6 @@ namespace Avalonia.Win32 new IntPtr((int)Icons.ICON_BIG), hIcon); } - /// public void ShowTaskbarIcon(bool value) { var newWindowProperties = _windowProperties; @@ -805,7 +762,6 @@ namespace Avalonia.Win32 UpdateWindowProperties(newWindowProperties); } - /// public void CanResize(bool value) { var newWindowProperties = _windowProperties; @@ -815,7 +771,6 @@ namespace Avalonia.Win32 UpdateWindowProperties(newWindowProperties); } - /// public void SetSystemDecorations(SystemDecorations value) { var newWindowProperties = _windowProperties; @@ -825,7 +780,6 @@ namespace Avalonia.Win32 UpdateWindowProperties(newWindowProperties); } - /// public void SetTopmost(bool value) { if (value == _topmost) @@ -842,7 +796,6 @@ namespace Avalonia.Win32 _topmost = value; } - /// public unsafe void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { if (Win32Platform.WindowsVersion.Build >= 22000) @@ -1457,7 +1410,6 @@ namespace Avalonia.Win32 IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; - /// public void SetExtendClientAreaToDecorationsHint(bool hint) { _isClientAreaExtended = hint; @@ -1465,7 +1417,6 @@ namespace Avalonia.Win32 ExtendClientArea(); } - /// public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) { _extendChromeHints = hints; From 19078979e38a9facb0be00ed0daeca3bd53c9e3a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Feb 2023 09:40:42 +0100 Subject: [PATCH 23/61] Initial implementation of SetCurrentValue. --- src/Avalonia.Base/AvaloniaObject.cs | 20 +- .../Diagnostics/AvaloniaPropertyValue.cs | 23 +- .../PropertyStore/EffectiveValue.cs | 6 + .../PropertyStore/EffectiveValue`1.cs | 29 +- src/Avalonia.Base/PropertyStore/ValueStore.cs | 23 +- .../AvaloniaObjectTests_SetCurrentValue.cs | 270 ++++++++++++++++++ 6 files changed, 345 insertions(+), 26 deletions(-) create mode 100644 tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 74dc55355b..93bbee12b8 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -355,6 +355,23 @@ namespace Avalonia SetDirectValueUnchecked(property, value); } + public void SetCurrentValue(StyledProperty property, T value) + { + _ = property ?? throw new ArgumentNullException(nameof(property)); + VerifyAccess(); + + LogPropertySet(property, value, BindingPriority.LocalValue); + + if (value is UnsetValueType) + { + _values.ClearLocalValue(property); + } + else if (value is not DoNothingType) + { + _values.SetCurrentValue(property, value); + } + } + /// /// Binds a to an observable. /// @@ -547,7 +564,8 @@ namespace Avalonia property, GetValue(property), BindingPriority.LocalValue, - null); + null, + false); } return _values.GetDiagnostic(property); diff --git a/src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs b/src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs index 4189fd5234..0b3e62f1cc 100644 --- a/src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs +++ b/src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs @@ -3,28 +3,23 @@ using Avalonia.Data; namespace Avalonia.Diagnostics { /// - /// Holds diagnostic-related information about the value of a - /// on a . + /// Holds diagnostic-related information about the value of an + /// on an . /// public class AvaloniaPropertyValue { - /// - /// Initializes a new instance of the class. - /// - /// The property. - /// The current property value. - /// The priority of the current value. - /// A diagnostic string. - public AvaloniaPropertyValue( + internal AvaloniaPropertyValue( AvaloniaProperty property, object? value, BindingPriority priority, - string? diagnostic) + string? diagnostic, + bool isOverriddenCurrentValue) { Property = property; Value = value; Priority = priority; Diagnostic = diagnostic; + IsOverriddenCurrentValue = isOverriddenCurrentValue; } /// @@ -46,5 +41,11 @@ namespace Avalonia.Diagnostics /// Gets a diagnostic string. /// public string? Diagnostic { get; } + + /// + /// Gets a value indicating whether the was overridden by a call to + /// . + /// + public bool IsOverriddenCurrentValue { get; } } } diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs index 04d3c805c2..78f0ad46b7 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs @@ -29,6 +29,12 @@ namespace Avalonia.PropertyStore /// public BindingPriority BasePriority { get; protected set; } + /// + /// Gets a value indicating whether the was overridden by a call to + /// . + /// + public bool IsOverridenCurrentValue { get; set; } + /// /// Begins a reevaluation pass on the effective value. /// diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index 3e20dcce56..0d93e9d8ed 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -57,7 +57,7 @@ namespace Avalonia.PropertyStore Debug.Assert(priority != BindingPriority.LocalValue); UpdateValueEntry(value, priority); - SetAndRaiseCore(owner, (StyledProperty)value.Property, GetValue(value), priority); + SetAndRaiseCore(owner, (StyledProperty)value.Property, GetValue(value), priority, false); } public void SetLocalValueAndRaise( @@ -65,7 +65,16 @@ namespace Avalonia.PropertyStore StyledProperty property, T value) { - SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue); + SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue, false); + } + + public void SetCurrentValueAndRaise( + ValueStore owner, + StyledProperty property, + T value) + { + IsOverridenCurrentValue = true; + SetAndRaiseCore(owner, property, value, Priority, true); } public bool TryGetBaseValue([MaybeNullWhen(false)] out T value) @@ -98,7 +107,7 @@ namespace Avalonia.PropertyStore Debug.Assert(Priority != BindingPriority.Animation); Debug.Assert(BasePriority != BindingPriority.Unset); UpdateValueEntry(null, BindingPriority.Animation); - SetAndRaiseCore(owner, (StyledProperty)property, _baseValue!, BasePriority); + SetAndRaiseCore(owner, (StyledProperty)property, _baseValue!, BasePriority, false); } public override void CoerceValue(ValueStore owner, AvaloniaProperty property) @@ -158,15 +167,16 @@ namespace Avalonia.PropertyStore ValueStore owner, StyledProperty property, T value, - BindingPriority priority) + BindingPriority priority, + bool isOverriddenCurrentValue) { - Debug.Assert(priority < BindingPriority.Inherited); - var oldValue = Value; var valueChanged = false; var baseValueChanged = false; var v = value; + IsOverridenCurrentValue = isOverriddenCurrentValue; + if (_uncommon?._coerce is { } coerce) v = coerce(owner.Owner, value); @@ -209,7 +219,6 @@ namespace Avalonia.PropertyStore T baseValue, BindingPriority basePriority) { - Debug.Assert(priority < BindingPriority.Inherited); Debug.Assert(basePriority > BindingPriority.Animation); Debug.Assert(priority <= basePriority); @@ -225,7 +234,7 @@ namespace Avalonia.PropertyStore bv = coerce(owner.Owner, baseValue); } - if (priority != BindingPriority.Unset && !EqualityComparer.Default.Equals(Value, v)) + if (!EqualityComparer.Default.Equals(Value, v)) { Value = v; valueChanged = true; @@ -233,9 +242,7 @@ namespace Avalonia.PropertyStore _uncommon._uncoercedValue = value; } - if (priority != BindingPriority.Unset && - (BasePriority == BindingPriority.Unset || - !EqualityComparer.Default.Equals(_baseValue, bv))) + if (!EqualityComparer.Default.Equals(_baseValue, bv)) { _baseValue = v; baseValueChanged = true; diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index a758360545..fd5cd91a6c 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -7,7 +7,6 @@ using Avalonia.Data; using Avalonia.Diagnostics; using Avalonia.Styling; using Avalonia.Utilities; -using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot; namespace Avalonia.PropertyStore { @@ -159,8 +158,9 @@ namespace Avalonia.PropertyStore public void ClearLocalValue(AvaloniaProperty property) { if (TryGetEffectiveValue(property, out var effective) && - effective.Priority == BindingPriority.LocalValue) + (effective.Priority == BindingPriority.LocalValue || effective.IsOverridenCurrentValue)) { + effective.IsOverridenCurrentValue = false; ReevaluateEffectiveValue(property, effective, ignoreLocalValue: true); } } @@ -209,6 +209,20 @@ namespace Avalonia.PropertyStore } } + public void SetCurrentValue(StyledProperty property, T value) + { + if (_effectiveValues.TryGetValue(property, out var v)) + { + ((EffectiveValue)v).SetCurrentValueAndRaise(this, property, value); + } + else + { + var effectiveValue = new EffectiveValue(Owner, property); + AddEffectiveValue(property, effectiveValue); + effectiveValue.SetCurrentValueAndRaise(this, property, value); + } + } + public object? GetValue(AvaloniaProperty property) { if (_effectiveValues.TryGetValue(property, out var v)) @@ -616,11 +630,13 @@ namespace Avalonia.PropertyStore { object? value; BindingPriority priority; + bool overridden = false; if (_effectiveValues.TryGetValue(property, out var v)) { value = v.Value; priority = v.Priority; + overridden = v.IsOverridenCurrentValue; } else if (property.Inherits && TryGetInheritedValue(property, out v)) { @@ -637,7 +653,8 @@ namespace Avalonia.PropertyStore property, value, priority, - null); + null, + overridden); } private int InsertFrame(ValueFrame frame) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs new file mode 100644 index 0000000000..16f924acba --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs @@ -0,0 +1,270 @@ +using System; +using System.Reactive.Linq; +using Avalonia.Data; +using Avalonia.Diagnostics; +using Avalonia.Reactive; +using Xunit; +using Observable = Avalonia.Reactive.Observable; + +namespace Avalonia.Base.UnitTests +{ + public class AvaloniaObjectTests_SetCurrentValue + { + [Fact] + public void SetCurrentValue_Sets_Unset_Value() + { + var target = new Class1(); + + target.SetCurrentValue(Class1.FooProperty, "newvalue"); + + Assert.Equal("newvalue", target.GetValue(Class1.FooProperty)); + Assert.Equal(BindingPriority.Unset, GetPriority(target, Class1.FooProperty)); + Assert.True(IsOverridden(target, Class1.FooProperty)); + } + + [Theory] + [InlineData(BindingPriority.LocalValue)] + [InlineData(BindingPriority.Style)] + [InlineData(BindingPriority.Animation)] + public void SetCurrentValue_Overrides_Existing_Value(BindingPriority priority) + { + var target = new Class1(); + + target.SetValue(Class1.FooProperty, "oldvalue", priority); + target.SetCurrentValue(Class1.FooProperty, "newvalue"); + + Assert.Equal("newvalue", target.GetValue(Class1.FooProperty)); + Assert.Equal(priority, GetPriority(target, Class1.FooProperty)); + Assert.True(IsOverridden(target, Class1.FooProperty)); + } + + [Fact] + public void SetCurrentValue_Overrides_Inherited_Value() + { + var parent = new Class1(); + var target = new Class1 { InheritanceParent = parent }; + + parent.SetValue(Class1.InheritedProperty, "inheritedvalue"); + target.SetCurrentValue(Class1.InheritedProperty, "newvalue"); + + Assert.Equal("newvalue", target.GetValue(Class1.InheritedProperty)); + Assert.Equal(BindingPriority.Unset, GetPriority(target, Class1.InheritedProperty)); + Assert.True(IsOverridden(target, Class1.InheritedProperty)); + } + + [Fact] + public void SetCurrentValue_Is_Inherited() + { + var parent = new Class1(); + var target = new Class1 { InheritanceParent = parent }; + + parent.SetCurrentValue(Class1.InheritedProperty, "newvalue"); + + Assert.Equal("newvalue", target.GetValue(Class1.InheritedProperty)); + Assert.Equal(BindingPriority.Inherited, GetPriority(target, Class1.InheritedProperty)); + Assert.False(IsOverridden(target, Class1.InheritedProperty)); + } + + [Fact] + public void ClearValue_Clears_CurrentValue_With_Unset_Priority() + { + var target = new Class1(); + + target.SetCurrentValue(Class1.FooProperty, "newvalue"); + target.ClearValue(Class1.FooProperty); + + Assert.Equal("foodefault", target.Foo); + Assert.False(IsOverridden(target, Class1.FooProperty)); + } + + [Fact] + public void ClearValue_Clears_CurrentValue_With_Inherited_Priority() + { + var parent = new Class1(); + var target = new Class1 { InheritanceParent = parent }; + + parent.SetValue(Class1.InheritedProperty, "inheritedvalue"); + target.SetCurrentValue(Class1.InheritedProperty, "newvalue"); + target.ClearValue(Class1.InheritedProperty); + + Assert.Equal("inheritedvalue", target.Inherited); + Assert.False(IsOverridden(target, Class1.FooProperty)); + } + + [Fact] + public void ClearValue_Clears_CurrentValue_With_LocalValue_Priority() + { + var target = new Class1(); + + target.SetValue(Class1.FooProperty, "localvalue"); + target.SetCurrentValue(Class1.FooProperty, "newvalue"); + target.ClearValue(Class1.FooProperty); + + Assert.Equal("foodefault", target.Foo); + Assert.False(IsOverridden(target, Class1.FooProperty)); + } + + [Fact] + public void ClearValue_Clears_CurrentValue_With_Style_Priority() + { + var target = new Class1(); + + target.SetValue(Class1.FooProperty, "stylevalue", BindingPriority.Style); + target.SetCurrentValue(Class1.FooProperty, "newvalue"); + target.ClearValue(Class1.FooProperty); + + Assert.Equal("stylevalue", target.Foo); + Assert.False(IsOverridden(target, Class1.FooProperty)); + } + + [Fact] + public void SetCurrentValue_Can_Be_Coerced() + { + var target = new Class1(); + + target.SetCurrentValue(Class1.CoercedProperty, 60); + Assert.Equal(60, target.GetValue(Class1.CoercedProperty)); + + target.CoerceMax = 50; + target.CoerceValue(Class1.CoercedProperty); + Assert.Equal(50, target.GetValue(Class1.CoercedProperty)); + + target.CoerceMax = 100; + target.CoerceValue(Class1.CoercedProperty); + Assert.Equal(60, target.GetValue(Class1.CoercedProperty)); + } + + [Theory] + [InlineData(BindingPriority.LocalValue)] + [InlineData(BindingPriority.Style)] + [InlineData(BindingPriority.Animation)] + public void SetValue_Overrides_CurrentValue_With_Unset_Priority(BindingPriority priority) + { + var target = new Class1(); + + target.SetCurrentValue(Class1.FooProperty, "current"); + target.SetValue(Class1.FooProperty, "setvalue", priority); + + Assert.Equal("setvalue", target.Foo); + Assert.Equal(priority, GetPriority(target, Class1.FooProperty)); + Assert.False(IsOverridden(target, Class1.FooProperty)); + } + + [Fact] + public void Animation_Value_Overrides_CurrentValue_With_LocalValue_Priority() + { + var target = new Class1(); + + target.SetValue(Class1.FooProperty, "localvalue"); + target.SetCurrentValue(Class1.FooProperty, "current"); + target.SetValue(Class1.FooProperty, "setvalue", BindingPriority.Animation); + + Assert.Equal("setvalue", target.Foo); + Assert.Equal(BindingPriority.Animation, GetPriority(target, Class1.FooProperty)); + Assert.False(IsOverridden(target, Class1.FooProperty)); + } + + [Fact] + public void StyleTrigger_Value_Overrides_CurrentValue_With_Style_Priority() + { + var target = new Class1(); + + target.SetValue(Class1.FooProperty, "style", BindingPriority.Style); + target.SetCurrentValue(Class1.FooProperty, "current"); + target.SetValue(Class1.FooProperty, "setvalue", BindingPriority.StyleTrigger); + + Assert.Equal("setvalue", target.Foo); + Assert.Equal(BindingPriority.StyleTrigger, GetPriority(target, Class1.FooProperty)); + Assert.False(IsOverridden(target, Class1.FooProperty)); + } + + [Theory] + [InlineData(BindingPriority.LocalValue)] + [InlineData(BindingPriority.Style)] + [InlineData(BindingPriority.Animation)] + public void Binding_Overrides_CurrentValue_With_Unset_Priority(BindingPriority priority) + { + var target = new Class1(); + + target.SetCurrentValue(Class1.FooProperty, "current"); + + var s = target.Bind(Class1.FooProperty, Observable.SingleValue("binding"), priority); + + Assert.Equal("binding", target.Foo); + Assert.Equal(priority, GetPriority(target, Class1.FooProperty)); + Assert.False(IsOverridden(target, Class1.FooProperty)); + + s.Dispose(); + + Assert.Equal("foodefault", target.Foo); + } + + [Fact] + public void Animation_Binding_Overrides_CurrentValue_With_LocalValue_Priority() + { + var target = new Class1(); + + target.SetValue(Class1.FooProperty, "localvalue"); + target.SetCurrentValue(Class1.FooProperty, "current"); + + var s = target.Bind(Class1.FooProperty, Observable.SingleValue("binding"), BindingPriority.Animation); + + Assert.Equal("binding", target.Foo); + Assert.Equal(BindingPriority.Animation, GetPriority(target, Class1.FooProperty)); + Assert.False(IsOverridden(target, Class1.FooProperty)); + + s.Dispose(); + + Assert.Equal("current", target.Foo); + } + + [Fact] + public void StyleTrigger_Binding_Overrides_CurrentValue_With_Style_Priority() + { + var target = new Class1(); + + target.SetValue(Class1.FooProperty, "style", BindingPriority.Style); + target.SetCurrentValue(Class1.FooProperty, "current"); + + var s = target.Bind(Class1.FooProperty, Observable.SingleValue("binding"), BindingPriority.StyleTrigger); + + Assert.Equal("binding", target.Foo); + Assert.Equal(BindingPriority.StyleTrigger, GetPriority(target, Class1.FooProperty)); + Assert.False(IsOverridden(target, Class1.FooProperty)); + + s.Dispose(); + + Assert.Equal("style", target.Foo); + } + + private BindingPriority GetPriority(AvaloniaObject target, AvaloniaProperty property) + { + return target.GetDiagnostic(property).Priority; + } + + private bool IsOverridden(AvaloniaObject target, AvaloniaProperty property) + { + return target.GetDiagnostic(property).IsOverriddenCurrentValue; + } + + private class Class1 : AvaloniaObject + { + public static readonly StyledProperty FooProperty = + AvaloniaProperty.Register(nameof(Foo), "foodefault"); + public static readonly StyledProperty InheritedProperty = + AvaloniaProperty.Register(nameof(Inherited), "inheriteddefault", inherits: true); + public static readonly StyledProperty CoercedProperty = + AvaloniaProperty.Register(nameof(Coerced), coerce: Coerce); + + public string Foo => GetValue(FooProperty); + public string Inherited => GetValue(InheritedProperty); + public double Coerced => GetValue(CoercedProperty); + public double CoerceMax { get; set; } = 100; + + private static double Coerce(AvaloniaObject sender, double value) + { + return Math.Min(value, ((Class1)sender).CoerceMax); + } + } + } +} From 8741b7e4106fd0ceef9f68ddc5eb3dfae5604e8e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Feb 2023 10:53:03 +0100 Subject: [PATCH 24/61] Fix IsSet with SetCurrentValue. And add unit tests. --- src/Avalonia.Base/PropertyStore/ValueStore.cs | 7 +------ .../AvaloniaObjectTests_SetCurrentValue.cs | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index fd5cd91a6c..64e3c498e9 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -249,12 +249,7 @@ namespace Avalonia.PropertyStore return false; } - public bool IsSet(AvaloniaProperty property) - { - if (_effectiveValues.TryGetValue(property, out var v)) - return v.Priority < BindingPriority.Inherited; - return false; - } + public bool IsSet(AvaloniaProperty property) => _effectiveValues.TryGetValue(property, out _); public void CoerceValue(AvaloniaProperty property) { diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs index 16f924acba..3edf0b105a 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs @@ -1,8 +1,6 @@ using System; -using System.Reactive.Linq; using Avalonia.Data; using Avalonia.Diagnostics; -using Avalonia.Reactive; using Xunit; using Observable = Avalonia.Reactive.Observable; @@ -18,6 +16,7 @@ namespace Avalonia.Base.UnitTests target.SetCurrentValue(Class1.FooProperty, "newvalue"); Assert.Equal("newvalue", target.GetValue(Class1.FooProperty)); + Assert.True(target.IsSet(Class1.FooProperty)); Assert.Equal(BindingPriority.Unset, GetPriority(target, Class1.FooProperty)); Assert.True(IsOverridden(target, Class1.FooProperty)); } @@ -34,6 +33,7 @@ namespace Avalonia.Base.UnitTests target.SetCurrentValue(Class1.FooProperty, "newvalue"); Assert.Equal("newvalue", target.GetValue(Class1.FooProperty)); + Assert.True(target.IsSet(Class1.FooProperty)); Assert.Equal(priority, GetPriority(target, Class1.FooProperty)); Assert.True(IsOverridden(target, Class1.FooProperty)); } @@ -48,6 +48,7 @@ namespace Avalonia.Base.UnitTests target.SetCurrentValue(Class1.InheritedProperty, "newvalue"); Assert.Equal("newvalue", target.GetValue(Class1.InheritedProperty)); + Assert.True(target.IsSet(Class1.InheritedProperty)); Assert.Equal(BindingPriority.Unset, GetPriority(target, Class1.InheritedProperty)); Assert.True(IsOverridden(target, Class1.InheritedProperty)); } @@ -61,6 +62,7 @@ namespace Avalonia.Base.UnitTests parent.SetCurrentValue(Class1.InheritedProperty, "newvalue"); Assert.Equal("newvalue", target.GetValue(Class1.InheritedProperty)); + Assert.False(target.IsSet(Class1.FooProperty)); Assert.Equal(BindingPriority.Inherited, GetPriority(target, Class1.InheritedProperty)); Assert.False(IsOverridden(target, Class1.InheritedProperty)); } @@ -74,6 +76,7 @@ namespace Avalonia.Base.UnitTests target.ClearValue(Class1.FooProperty); Assert.Equal("foodefault", target.Foo); + Assert.False(target.IsSet(Class1.FooProperty)); Assert.False(IsOverridden(target, Class1.FooProperty)); } @@ -88,6 +91,7 @@ namespace Avalonia.Base.UnitTests target.ClearValue(Class1.InheritedProperty); Assert.Equal("inheritedvalue", target.Inherited); + Assert.False(target.IsSet(Class1.FooProperty)); Assert.False(IsOverridden(target, Class1.FooProperty)); } @@ -101,6 +105,7 @@ namespace Avalonia.Base.UnitTests target.ClearValue(Class1.FooProperty); Assert.Equal("foodefault", target.Foo); + Assert.False(target.IsSet(Class1.FooProperty)); Assert.False(IsOverridden(target, Class1.FooProperty)); } @@ -114,6 +119,7 @@ namespace Avalonia.Base.UnitTests target.ClearValue(Class1.FooProperty); Assert.Equal("stylevalue", target.Foo); + Assert.True(target.IsSet(Class1.FooProperty)); Assert.False(IsOverridden(target, Class1.FooProperty)); } @@ -146,6 +152,7 @@ namespace Avalonia.Base.UnitTests target.SetValue(Class1.FooProperty, "setvalue", priority); Assert.Equal("setvalue", target.Foo); + Assert.True(target.IsSet(Class1.FooProperty)); Assert.Equal(priority, GetPriority(target, Class1.FooProperty)); Assert.False(IsOverridden(target, Class1.FooProperty)); } @@ -160,6 +167,7 @@ namespace Avalonia.Base.UnitTests target.SetValue(Class1.FooProperty, "setvalue", BindingPriority.Animation); Assert.Equal("setvalue", target.Foo); + Assert.True(target.IsSet(Class1.FooProperty)); Assert.Equal(BindingPriority.Animation, GetPriority(target, Class1.FooProperty)); Assert.False(IsOverridden(target, Class1.FooProperty)); } @@ -174,6 +182,7 @@ namespace Avalonia.Base.UnitTests target.SetValue(Class1.FooProperty, "setvalue", BindingPriority.StyleTrigger); Assert.Equal("setvalue", target.Foo); + Assert.True(target.IsSet(Class1.FooProperty)); Assert.Equal(BindingPriority.StyleTrigger, GetPriority(target, Class1.FooProperty)); Assert.False(IsOverridden(target, Class1.FooProperty)); } @@ -191,6 +200,7 @@ namespace Avalonia.Base.UnitTests var s = target.Bind(Class1.FooProperty, Observable.SingleValue("binding"), priority); Assert.Equal("binding", target.Foo); + Assert.True(target.IsSet(Class1.FooProperty)); Assert.Equal(priority, GetPriority(target, Class1.FooProperty)); Assert.False(IsOverridden(target, Class1.FooProperty)); @@ -210,6 +220,7 @@ namespace Avalonia.Base.UnitTests var s = target.Bind(Class1.FooProperty, Observable.SingleValue("binding"), BindingPriority.Animation); Assert.Equal("binding", target.Foo); + Assert.True(target.IsSet(Class1.FooProperty)); Assert.Equal(BindingPriority.Animation, GetPriority(target, Class1.FooProperty)); Assert.False(IsOverridden(target, Class1.FooProperty)); @@ -229,6 +240,7 @@ namespace Avalonia.Base.UnitTests var s = target.Bind(Class1.FooProperty, Observable.SingleValue("binding"), BindingPriority.StyleTrigger); Assert.Equal("binding", target.Foo); + Assert.True(target.IsSet(Class1.FooProperty)); Assert.Equal(BindingPriority.StyleTrigger, GetPriority(target, Class1.FooProperty)); Assert.False(IsOverridden(target, Class1.FooProperty)); From 0ce9180d7c56b17c7b6a03c44d82161f6d031036 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Feb 2023 11:41:59 +0100 Subject: [PATCH 25/61] Added untyped SetCurrentValue. --- src/Avalonia.Base/AvaloniaObject.cs | 34 +++++++++++++++++++ src/Avalonia.Base/AvaloniaProperty.cs | 7 ++++ src/Avalonia.Base/DirectPropertyBase.cs | 5 +++ src/Avalonia.Base/StyledProperty.cs | 21 ++++++++++++ .../AvaloniaObjectTests_SetCurrentValue.cs | 26 ++++++++++++++ .../AvaloniaPropertyTests.cs | 5 +++ 6 files changed, 98 insertions(+) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 93bbee12b8..5a5827d0aa 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -355,6 +355,40 @@ namespace Avalonia SetDirectValueUnchecked(property, value); } + /// + /// Sets the value of a dependency property without changing its value source. + /// + /// The property. + /// The value. + /// + /// This method is used by a component that programmatically sets the value of one of its + /// own properties without disabling an application's declared use of the property. The + /// method changes the effective value of the property, but existing data bindings and + /// styles will continue to work. + /// + /// The new value will have the property's current , even if + /// that priority is or + /// . + /// + public void SetCurrentValue(AvaloniaProperty property, object? value) => + property.RouteSetCurrentValue(this, value); + + /// + /// Sets the value of a dependency property without changing its value source. + /// + /// The type of the property. + /// The property. + /// The value. + /// + /// This method is used by a component that programmatically sets the value of one of its + /// own properties without disabling an application's declared use of the property. The + /// method changes the effective value of the property, but existing data bindings and + /// styles will continue to work. + /// + /// The new value will have the property's current , even if + /// that priority is or + /// . + /// public void SetCurrentValue(StyledProperty property, T value) { _ = property ?? throw new ArgumentNullException(nameof(property)); diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 5db4d81f03..1c1d09c3f5 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -496,6 +496,13 @@ namespace Avalonia object? value, BindingPriority priority); + /// + /// Routes an untyped SetCurrentValue call to a typed call. + /// + /// The object instance. + /// The value. + internal abstract void RouteSetCurrentValue(AvaloniaObject o, object? value); + /// /// Routes an untyped Bind call to a typed call. /// diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index 9ee1eee0fa..94dfaaab01 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -152,6 +152,11 @@ namespace Avalonia return null; } + internal override void RouteSetCurrentValue(AvaloniaObject o, object? value) + { + RouteSetValue(o, value, BindingPriority.LocalValue); + } + /// /// Routes an untyped Bind call to a typed call. /// diff --git a/src/Avalonia.Base/StyledProperty.cs b/src/Avalonia.Base/StyledProperty.cs index 79d1b9202d..8e0ecf5544 100644 --- a/src/Avalonia.Base/StyledProperty.cs +++ b/src/Avalonia.Base/StyledProperty.cs @@ -220,6 +220,27 @@ namespace Avalonia } } + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] + internal override void RouteSetCurrentValue(AvaloniaObject target, object? value) + { + if (value == BindingOperations.DoNothing) + return; + + if (value == UnsetValue) + { + target.ClearValue(this); + } + else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var converted)) + { + target.SetCurrentValue(this, (TValue)converted!); + } + else + { + var type = value?.GetType().FullName ?? "(null)"; + throw new ArgumentException($"Invalid value for Property '{Name}': '{value}' ({type})"); + } + } + internal override IDisposable RouteBind( AvaloniaObject target, IObservable source, diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs index 3edf0b105a..8ad36a583e 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs @@ -21,6 +21,19 @@ namespace Avalonia.Base.UnitTests Assert.True(IsOverridden(target, Class1.FooProperty)); } + [Fact] + public void SetCurrentValue_Sets_Unset_Value_Untyped() + { + var target = new Class1(); + + target.SetCurrentValue((AvaloniaProperty)Class1.FooProperty, "newvalue"); + + Assert.Equal("newvalue", target.GetValue(Class1.FooProperty)); + Assert.True(target.IsSet(Class1.FooProperty)); + Assert.Equal(BindingPriority.Unset, GetPriority(target, Class1.FooProperty)); + Assert.True(IsOverridden(target, Class1.FooProperty)); + } + [Theory] [InlineData(BindingPriority.LocalValue)] [InlineData(BindingPriority.Style)] @@ -140,6 +153,19 @@ namespace Avalonia.Base.UnitTests Assert.Equal(60, target.GetValue(Class1.CoercedProperty)); } + [Fact] + public void SetCurrentValue_Unset_Clears_CurrentValue() + { + var target = new Class1(); + + target.SetCurrentValue(Class1.FooProperty, "newvalue"); + target.SetCurrentValue(Class1.FooProperty, AvaloniaProperty.UnsetValue); + + Assert.Equal("foodefault", target.Foo); + Assert.False(target.IsSet(Class1.FooProperty)); + Assert.False(IsOverridden(target, Class1.FooProperty)); + } + [Theory] [InlineData(BindingPriority.LocalValue)] [InlineData(BindingPriority.Style)] diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs index 5733159a23..a9b8a5f21b 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs @@ -179,6 +179,11 @@ namespace Avalonia.Base.UnitTests throw new NotImplementedException(); } + internal override void RouteSetCurrentValue(AvaloniaObject o, object value) + { + throw new NotImplementedException(); + } + internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o) { throw new NotImplementedException(); From fc0ddcb320754c8a3da969e32be23073d8202d6e Mon Sep 17 00:00:00 2001 From: kekekeks Date: Mon, 13 Feb 2023 16:19:19 +0300 Subject: [PATCH 26/61] Use NT shared handles for Vulkan interop on Windows --- .../GpuInterop/VulkanDemo/D3DMemoryHelper.cs | 2 +- .../GpuInterop/VulkanDemo/VulkanContext.cs | 2 +- samples/GpuInterop/VulkanDemo/VulkanImage.cs | 30 +++++++++----- .../Avalonia.Win32/DirectX/directx.idl | 40 +++++++++++++++++++ .../Angle/AngleExternalObjectsFeature.cs | 14 +++++-- 5 files changed, 73 insertions(+), 15 deletions(-) diff --git a/samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs b/samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs index a0b7d32d3b..ac09c48ccd 100644 --- a/samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs +++ b/samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs @@ -47,7 +47,7 @@ public class D3DMemoryHelper MipLevels = 1, SampleDescription = new SampleDescription { Count = 1, Quality = 0 }, CpuAccessFlags = default, - OptionFlags = ResourceOptionFlags.SharedKeyedmutex, + OptionFlags = ResourceOptionFlags.SharedKeyedmutex|ResourceOptionFlags.SharedNthandle, BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource }); } diff --git a/samples/GpuInterop/VulkanDemo/VulkanContext.cs b/samples/GpuInterop/VulkanDemo/VulkanContext.cs index 3fdd9695f2..1d44549089 100644 --- a/samples/GpuInterop/VulkanDemo/VulkanContext.cs +++ b/samples/GpuInterop/VulkanDemo/VulkanContext.cs @@ -173,7 +173,7 @@ public unsafe class VulkanContext : IDisposable api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref queueFamilyCount, familyProperties); for (uint queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++) { - var family = familyProperties[c]; + var family = familyProperties[queueFamilyIndex]; if (!family.QueueFlags.HasAllFlags(QueueFlags.GraphicsBit)) continue; diff --git a/samples/GpuInterop/VulkanDemo/VulkanImage.cs b/samples/GpuInterop/VulkanDemo/VulkanImage.cs index 59b2ef7e30..dfb6ba20e0 100644 --- a/samples/GpuInterop/VulkanDemo/VulkanImage.cs +++ b/samples/GpuInterop/VulkanDemo/VulkanImage.cs @@ -4,10 +4,13 @@ using System.Runtime.InteropServices; using Avalonia; using Avalonia.Platform; using Avalonia.Vulkan; +using SharpDX.DXGI; using Silk.NET.Vulkan; using Silk.NET.Vulkan.Extensions.KHR; using SilkNetDemo; using SkiaSharp; +using Device = Silk.NET.Vulkan.Device; +using Format = Silk.NET.Vulkan.Format; namespace GpuInterop.VulkanDemo; @@ -24,7 +27,6 @@ public unsafe class VulkanImage : IDisposable private ImageView? _imageView { get; set; } private DeviceMemory _imageMemory { get; set; } private SharpDX.Direct3D11.Texture2D? _d3dTexture2D; - private IntPtr _win32ShareHandle; internal Image? InternalHandle { get; private set; } internal Format Format { get; } @@ -108,14 +110,14 @@ public unsafe class VulkanImage : IDisposable if (exportable && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { _d3dTexture2D = D3DMemoryHelper.CreateMemoryHandle(vk.D3DDevice, size, Format); - using var dxgi = _d3dTexture2D.QueryInterface(); - _win32ShareHandle = dxgi.SharedHandle; + using var dxgi = _d3dTexture2D.QueryInterface(); + handleImport = new ImportMemoryWin32HandleInfoKHR { PNext = &dedicatedAllocation, SType = StructureType.ImportMemoryWin32HandleInfoKhr, - HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit, - Handle = _win32ShareHandle, + HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureBit, + Handle = dxgi.CreateSharedHandle(null, SharedResourceFlags.Read | SharedResourceFlags.Write), }; } @@ -185,11 +187,19 @@ public unsafe class VulkanImage : IDisposable return fd; } - public IPlatformHandle Export() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? - new PlatformHandle(_win32ShareHandle, - KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle) : - new PlatformHandle(new IntPtr(ExportFd()), - KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor); + public IPlatformHandle Export() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + using var dxgi = _d3dTexture2D!.QueryInterface(); + return new PlatformHandle( + dxgi.CreateSharedHandle(null, SharedResourceFlags.Read | SharedResourceFlags.Write), + KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureNtHandle); + } + else + return new PlatformHandle(new IntPtr(ExportFd()), + KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor); + } public ImageTiling Tiling => ImageTiling.Optimal; diff --git a/src/Windows/Avalonia.Win32/DirectX/directx.idl b/src/Windows/Avalonia.Win32/DirectX/directx.idl index 1d66c898db..120d1d5159 100644 --- a/src/Windows/Avalonia.Win32/DirectX/directx.idl +++ b/src/Windows/Avalonia.Win32/DirectX/directx.idl @@ -36,6 +36,8 @@ @clr-map DXGI_SHARED_RESOURCE void* @clr-map LUID ulong @clr-map LPSTR ushort* +@clr-map LPWSTR ushort* +@clr-map LPCWSTR ushort* enum DXGI_FORMAT @@ -501,6 +503,44 @@ interface ID3D11Device : IUnknown UINT GetExceptionMode(); } +[uuid(a04bfb29-08ef-43d6-a49c-a9bdbdcbe686)] +interface ID3D11Device1 : ID3D11Device +{ + void GetImmediateContext1( void** ppImmediateContext ); + + HRESULT CreateDeferredContext1( + UINT ContextFlags, // Reserved parameter; must be 0 + [out, retval] IUnknown** ppDeferredContext ); + + HRESULT CreateBlendState1( + void* pBlendStateDesc, + [out, retval] IUnknown** ppBlendState ); + + HRESULT CreateRasterizerState1( + void* pRasterizerDesc, + [out, retval] IUnknown** ppRasterizerState ); + + HRESULT CreateDeviceContextState( + UINT Flags, + void* pFeatureLevels, + UINT FeatureLevels, + UINT SDKVersion, + GUID* EmulatedInterface, + void* pChosenFeatureLevel, + [out, retval] IUnknown** ppContextState); + + HRESULT OpenSharedResource1( + IntPtr hResource, + Guid* ReturnedInterface, + [out, retval] IUnknown** ppResource); + + HRESULT OpenSharedResourceByName( + LPCWSTR lpName, + DWORD dwDesiredAccess, + REFIID returnedInterface, + void** ppResource); +}; + [uuid( 6f15aaf2-d208-4e89-9ab4-489535d34f9c)] interface ID3D11Texture2D : IUnknown diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalObjectsFeature.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalObjectsFeature.cs index a7b52e953e..4349da85c8 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalObjectsFeature.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalObjectsFeature.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.CompilerServices; using Avalonia.Controls.Documents; using Avalonia.OpenGL; @@ -15,12 +16,14 @@ internal class AngleExternalObjectsFeature : IGlContextExternalObjectsFeature, I { private readonly EglContext _context; private readonly ID3D11Device _device; + private readonly ID3D11Device1 _device1; public AngleExternalObjectsFeature(EglContext context) { _context = context; var angle = (AngleWin32EglDisplay)context.Display; _device = MicroComRuntime.CreateProxyFor(angle.GetDirect3DDevice(), false).CloneReference(); + _device1 = _device.QueryInterface(); using var dxgiDevice = _device.QueryInterface(); using var adapter = dxgiDevice.Adapter; DeviceLuid = BitConverter.GetBytes(adapter.Desc.AdapterLuid); @@ -28,7 +31,8 @@ internal class AngleExternalObjectsFeature : IGlContextExternalObjectsFeature, I public IReadOnlyList SupportedImportableExternalImageTypes { get; } = new[] { - KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle + KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle, + KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureNtHandle, }; public IReadOnlyList SupportedExportableExternalImageTypes => SupportedImportableExternalImageTypes; @@ -72,13 +76,17 @@ internal class AngleExternalObjectsFeature : IGlContextExternalObjectsFeature, I public unsafe IGlExternalImageTexture ImportImage(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties) { - if (handle.HandleDescriptor != KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle) + if (!SupportedImportableExternalImageTypes.Contains(handle.HandleDescriptor)) throw new NotSupportedException("Unsupported external memory type"); using (_context.EnsureCurrent()) { var guid = MicroComRuntime.GetGuidFor(typeof(ID3D11Texture2D)); - using var opened = _device.OpenSharedResource(handle.Handle, &guid); + using var opened = + handle.HandleDescriptor == + KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle ? + _device.OpenSharedResource(handle.Handle, &guid) : + _device1.OpenSharedResource1(handle.Handle, &guid); using var texture = opened.QueryInterface(); return new AngleExternalMemoryD3D11Texture2D(_context, texture, properties); } From 3e0179e1e033763a7bdbf13af98eaa06b836cf19 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 9 Feb 2023 20:27:29 +0000 Subject: [PATCH 27/61] add composing region to text input client, improves android composition --- .../Avalonia.Android/AndroidInputMethod.cs | 40 ++++-- src/Android/Avalonia.Android/InputEditable.cs | 93 ++++++++++++ .../Platform/SkiaPlatform/TopLevelImpl.cs | 132 +++--------------- .../Input/TextInput/ITextInputMethodClient.cs | 6 + .../Presenters/TextPresenter.cs | 33 ++++- .../TextBoxTextInputMethodClient.cs | 11 ++ 6 files changed, 192 insertions(+), 123 deletions(-) create mode 100644 src/Android/Avalonia.Android/InputEditable.cs diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index c885a7768c..459b20c410 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -5,8 +5,10 @@ using Android.Text; using Android.Views; using Android.Views.InputMethods; using Avalonia.Android.Platform.SkiaPlatform; +using Avalonia.Controls.Presenters; using Avalonia.Input; using Avalonia.Input.TextInput; +using Avalonia.Reactive; namespace Avalonia.Android { @@ -39,6 +41,7 @@ namespace Avalonia.Android private readonly InputMethodManager _imm; private ITextInputMethodClient _client; private AvaloniaInputConnection _inputConnection; + private IDisposable _textChangeObservable; public AndroidInputMethod(TView host) { @@ -70,13 +73,9 @@ namespace Avalonia.Android { if (_client != null) { + _textChangeObservable?.Dispose(); _client.SurroundingTextChanged -= SurroundingTextChanged; - } - - if(_inputConnection != null) - { - _inputConnection.ComposingText = null; - _inputConnection.ComposingRegion = default; + _client.TextViewVisualChanged -= TextViewVisualChanged; } _client = client; @@ -84,6 +83,12 @@ namespace Avalonia.Android if (IsActive) { _client.SurroundingTextChanged += SurroundingTextChanged; + _client.TextViewVisualChanged += TextViewVisualChanged; + + if(_client.TextViewVisual is TextPresenter textVisual) + { + _textChangeObservable = textVisual.GetObservable(TextPresenter.TextProperty).Subscribe(new AnonymousObserver(UpdateText)); + } _host.RequestFocus(); @@ -101,6 +106,23 @@ namespace Avalonia.Android } } + private void TextViewVisualChanged(object sender, EventArgs e) + { + var textVisual = _client.TextViewVisual as TextPresenter; + _textChangeObservable?.Dispose(); + _textChangeObservable = null; + + if(textVisual != null) + { + _textChangeObservable = textVisual.GetObservable(TextPresenter.TextProperty).Subscribe(new AnonymousObserver(UpdateText)); + } + } + + private void UpdateText(string? obj) + { + (_inputConnection?.Editable as InputEditable)?.UpdateString(obj); + } + private void SurroundingTextChanged(object sender, EventArgs e) { if (IsActive && _inputConnection != null) @@ -109,12 +131,10 @@ namespace Avalonia.Android _inputConnection.SurroundingText = surroundingText; - _imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset); - - if (_inputConnection.ComposingText != null && !_inputConnection.IsCommiting && surroundingText.AnchorOffset == surroundingText.CursorOffset) + if ((_inputConnection?.Editable as InputEditable)?.IsInBatchEdit != true) { - _inputConnection.CommitText(_inputConnection.ComposingText, 0); _inputConnection.SetSelection(surroundingText.AnchorOffset, surroundingText.CursorOffset); + _imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset); } } } diff --git a/src/Android/Avalonia.Android/InputEditable.cs b/src/Android/Avalonia.Android/InputEditable.cs new file mode 100644 index 0000000000..b702b0205c --- /dev/null +++ b/src/Android/Avalonia.Android/InputEditable.cs @@ -0,0 +1,93 @@ +using System; +using Android.Runtime; +using Android.Text; +using Android.Views; +using Android.Views.InputMethods; +using Avalonia.Android.Platform.SkiaPlatform; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Java.Lang; +using static System.Net.Mime.MediaTypeNames; + +namespace Avalonia.Android +{ + internal class InputEditable : SpannableStringBuilder + { + private readonly TopLevelImpl _topLevel; + private readonly IAndroidInputMethod _inputMethod; + private int _currentBatchLevel; + private string _previousText; + + public InputEditable(TopLevelImpl topLevel, IAndroidInputMethod inputMethod) + { + _topLevel = topLevel; + _inputMethod = inputMethod; + } + + public InputEditable(ICharSequence text) : base(text) + { + } + + public InputEditable(string text) : base(text) + { + } + + public InputEditable(ICharSequence text, int start, int end) : base(text, start, end) + { + } + + public InputEditable(string text, int start, int end) : base(text, start, end) + { + } + + protected InputEditable(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) + { + } + + public bool IsInBatchEdit => _currentBatchLevel > 0; + + public void BeginBatchEdit() + { + _currentBatchLevel++; + + if(_currentBatchLevel == 1) + { + _previousText = ToString(); + } + } + + public void EndBatchEdit() + { + if (_currentBatchLevel == 1) + { + _inputMethod.Client.SelectInSurroundingText(-1, _previousText.Length); + var time = DateTime.Now.TimeOfDay; + var currentText = ToString(); + + if (string.IsNullOrEmpty(currentText)) + { + _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel)); + } + else + { + var rawTextEvent = new RawTextInputEventArgs(KeyboardDevice.Instance, (ulong)time.Ticks, _topLevel.InputRoot, currentText); + _topLevel.Input(rawTextEvent); + } + _inputMethod.Client.SelectInSurroundingText(Selection.GetSelectionStart(this), Selection.GetSelectionEnd(this)); + + _previousText = ""; + } + + _currentBatchLevel--; + } + + public void UpdateString(string? text) + { + if(text != ToString()) + { + Clear(); + Insert(0, text); + } + } + } +} diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 693a26f3bd..b6bcc4ac9f 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -410,27 +410,23 @@ namespace Avalonia.Android.Platform.SkiaPlatform { private readonly TopLevelImpl _topLevel; private readonly IAndroidInputMethod _inputMethod; + private readonly InputEditable _editable; public AvaloniaInputConnection(TopLevelImpl topLevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true) { _topLevel = topLevel; _inputMethod = inputMethod; + _editable = new InputEditable(_topLevel, _inputMethod); } public TextInputMethodSurroundingText SurroundingText { get; set; } - public string ComposingText { get; internal set; } - - public ComposingRegion? ComposingRegion { get; internal set; } - - public bool IsComposing => !string.IsNullOrEmpty(ComposingText); - public bool IsCommiting { get; private set; } + public override IEditable Editable => _editable; public override bool SetComposingRegion(int start, int end) { - //System.Diagnostics.Debug.WriteLine($"Composing Region: [{start}|{end}] {SurroundingText.Text?.Substring(start, end - start)}"); - - ComposingRegion = new ComposingRegion(start, end); + _inputMethod.Client.SetPreeditText(null); + _inputMethod.Client.SetComposingRegion(new Media.TextFormatting.TextRange(start, end)); return base.SetComposingRegion(start, end); } @@ -439,132 +435,46 @@ namespace Avalonia.Android.Platform.SkiaPlatform { var composingText = text.ToString(); - ComposingText = composingText; - - _inputMethod.Client?.SetPreeditText(ComposingText); - - return base.SetComposingText(text, newCursorPosition); - } - - public override bool FinishComposingText() - { - if (!string.IsNullOrEmpty(ComposingText)) + if (string.IsNullOrEmpty(composingText)) { - CommitText(ComposingText, ComposingText.Length); + return CommitText(text, newCursorPosition); } else { - ComposingRegion = new ComposingRegion(SurroundingText.CursorOffset, SurroundingText.CursorOffset); + return base.SetComposingText(text, newCursorPosition); } - - return base.FinishComposingText(); } - public override ICharSequence GetTextBeforeCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags) + public override bool BeginBatchEdit() { - if (!string.IsNullOrEmpty(SurroundingText.Text) && length > 0) - { - var start = System.Math.Max(SurroundingText.CursorOffset - length, 0); - - var end = System.Math.Min(start + length - 1, SurroundingText.CursorOffset); - - var text = SurroundingText.Text.Substring(start, end - start); + _editable.BeginBatchEdit(); - //System.Diagnostics.Debug.WriteLine($"Text Before: {text}"); - - return new Java.Lang.String(text); - } - - return null; + return base.BeginBatchEdit(); } - public override ICharSequence GetTextAfterCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags) + public override bool EndBatchEdit() { - if (!string.IsNullOrEmpty(SurroundingText.Text)) - { - var start = SurroundingText.CursorOffset; - - var end = System.Math.Min(start + length, SurroundingText.Text.Length); - - var text = SurroundingText.Text.Substring(start, end - start); - - //System.Diagnostics.Debug.WriteLine($"Text After: {text}"); + var ret = base.EndBatchEdit(); + _editable.EndBatchEdit(); - return new Java.Lang.String(text); - } + return ret; + } - return null; + public override bool FinishComposingText() + { + _inputMethod.Client?.SetComposingRegion(null); + return base.FinishComposingText(); } public override bool CommitText(ICharSequence text, int newCursorPosition) { - IsCommiting = true; - var committedText = text.ToString(); - _inputMethod.Client.SetPreeditText(null); - int? start, end; - - if(SurroundingText.CursorOffset != SurroundingText.AnchorOffset) - { - start = Math.Min(SurroundingText.CursorOffset, SurroundingText.AnchorOffset); - end = Math.Max(SurroundingText.CursorOffset, SurroundingText.AnchorOffset); - } - else if (ComposingRegion != null) - { - start = ComposingRegion?.Start; - end = ComposingRegion?.End; - - ComposingRegion = null; - } - else - { - start = end = _inputMethod.Client.SurroundingText.CursorOffset; - } - - _inputMethod.Client.SelectInSurroundingText((int)start, (int)end); - - var time = DateTime.Now.TimeOfDay; - - var rawTextEvent = new RawTextInputEventArgs(KeyboardDevice.Instance, (ulong)time.Ticks, _topLevel.InputRoot, committedText); - - _topLevel.Input(rawTextEvent); - - ComposingText = null; - - ComposingRegion = new ComposingRegion(newCursorPosition, newCursorPosition); + _inputMethod.Client?.SetComposingRegion(null); return base.CommitText(text, newCursorPosition); } - public override bool DeleteSurroundingText(int beforeLength, int afterLength) - { - var surroundingText = _inputMethod.Client.SurroundingText; - - var selectionStart = surroundingText.CursorOffset; - - _inputMethod.Client.SelectInSurroundingText(selectionStart - beforeLength, selectionStart + afterLength); - - _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel)); - - surroundingText = _inputMethod.Client.SurroundingText; - - selectionStart = surroundingText.CursorOffset; - - ComposingRegion = new ComposingRegion(selectionStart, selectionStart); - - return base.DeleteSurroundingText(beforeLength, afterLength); - } - - public override bool SetSelection(int start, int end) - { - _inputMethod.Client.SelectInSurroundingText(start, end); - - ComposingRegion = new ComposingRegion(start, end); - - return base.SetSelection(start, end); - } - public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode) { switch (actionCode) diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs index 531cf3c704..8239bc6a21 100644 --- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs +++ b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Media.TextFormatting; using Avalonia.VisualTree; namespace Avalonia.Input.TextInput @@ -30,6 +31,11 @@ namespace Avalonia.Input.TextInput /// void SetPreeditText(string? text); + /// + /// Sets the current composing region. This doesn't remove the composing text from the commited text. + /// + void SetComposingRegion(TextRange? region); + /// /// Indicates if text input client is capable of providing the text around the cursor /// diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 3481b1ecf3..bb6b03d59a 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -63,6 +63,15 @@ namespace Avalonia.Controls.Presenters o => o.PreeditText, (o, v) => o.PreeditText = v); + /// + /// Defines the property. + /// + public static readonly DirectProperty CompositionRegionProperty = + AvaloniaProperty.RegisterDirect( + nameof(CompositionRegion), + o => o.CompositionRegion, + (o, v) => o.CompositionRegion = v); + /// /// Defines the property. /// @@ -106,6 +115,7 @@ namespace Avalonia.Controls.Presenters private Rect _caretBounds; private Point _navigationPosition; private string? _preeditText; + private TextRange? _compositionRegion; static TextPresenter() { @@ -146,6 +156,12 @@ namespace Avalonia.Controls.Presenters set => SetAndRaise(PreeditTextProperty, ref _preeditText, value); } + public TextRange? CompositionRegion + { + get => _compositionRegion; + set => SetAndRaise(CompositionRegionProperty, ref _compositionRegion, value); + } + /// /// Gets or sets the font family. /// @@ -548,7 +564,20 @@ namespace Avalonia.Controls.Presenters var foreground = Foreground; - if (!string.IsNullOrEmpty(_preeditText)) + if(_compositionRegion != null) + { + var preeditHighlight = new ValueSpan(_compositionRegion?.Start ?? 0, _compositionRegion?.Length ?? 0, + new GenericTextRunProperties(typeface, FontSize, + foregroundBrush: foreground, + textDecorations: TextDecorations.Underline)); + + textStyleOverrides = new[] + { + preeditHighlight + }; + + } + else if (!string.IsNullOrEmpty(_preeditText)) { var preeditHighlight = new ValueSpan(_caretIndex, _preeditText.Length, new GenericTextRunProperties(typeface, FontSize, @@ -911,6 +940,7 @@ namespace Avalonia.Controls.Presenters break; } + case nameof(CompositionRegion): case nameof(Foreground): case nameof(FontSize): case nameof(FontStyle): @@ -931,7 +961,6 @@ namespace Avalonia.Controls.Presenters case nameof(PasswordChar): case nameof(RevealPassword): - case nameof(FlowDirection): { InvalidateTextLayout(); diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs index 10c2f36f43..239501aace 100644 --- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs +++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs @@ -5,6 +5,7 @@ using Avalonia.Media.TextFormatting; using Avalonia.Threading; using Avalonia.Utilities; using Avalonia.VisualTree; +using static System.Net.Mime.MediaTypeNames; namespace Avalonia.Controls { @@ -110,6 +111,16 @@ namespace Avalonia.Controls _presenter.PreeditText = text; } + public void SetComposingRegion(TextRange? region) + { + if (_presenter == null) + { + return; + } + + _presenter.CompositionRegion = region; + } + public void SelectInSurroundingText(int start, int end) { if(_parent is null ||_presenter is null) From 4edc10caba109f2dcaf669a448c056e1f99f81e2 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Sat, 11 Feb 2023 09:26:22 +0000 Subject: [PATCH 28/61] fix prediction text for browser --- src/Browser/Avalonia.Browser/AvaloniaView.cs | 31 +++++++++++++++++++ .../Avalonia.Browser/Interop/InputHelper.cs | 2 ++ .../webapp/modules/avalonia/input.ts | 20 ++++++++++++ 3 files changed, 53 insertions(+) diff --git a/src/Browser/Avalonia.Browser/AvaloniaView.cs b/src/Browser/Avalonia.Browser/AvaloniaView.cs index 294216ee03..16c2b2a0b4 100644 --- a/src/Browser/Avalonia.Browser/AvaloniaView.cs +++ b/src/Browser/Avalonia.Browser/AvaloniaView.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices.JavaScript; using Avalonia.Browser.Interop; @@ -15,6 +16,7 @@ using Avalonia.Platform; using Avalonia.Rendering.Composition; using Avalonia.Threading; using SkiaSharp; +using static System.Runtime.CompilerServices.RuntimeHelpers; namespace Avalonia.Browser { @@ -94,6 +96,7 @@ namespace Avalonia.Browser InputHelper.SubscribeTextEvents( _inputElement, + OnBeforeInput, OnTextInput, OnCompositionStart, OnCompositionUpdate, @@ -316,11 +319,37 @@ namespace Avalonia.Browser return _topLevelImpl.RawTextEvent(data); } + private bool OnBeforeInput(JSObject arg, int start, int end) + { + var type = arg.GetPropertyAsString("inputType"); + Console.WriteLine(type); + if (type != "deleteByComposition") + { + if (type == "deleteContentBackward") + { + start = _inputElement.GetPropertyAsInt32("selectionStart"); + end = _inputElement.GetPropertyAsInt32("selectionEnd"); + } + else + { + start = -1; + end = -1; + } + } + + if(start != -1 && end != -1 && _client != null) + { + _client.SelectInSurroundingText(start, end); + } + return false; + } + private bool OnCompositionStart (JSObject args) { if (_client == null) return false; + Console.WriteLine("composition start"); _client.SetPreeditText(null); IsComposing = true; @@ -331,6 +360,7 @@ namespace Avalonia.Browser { if (_client == null) return false; + Console.WriteLine("composition update"); _client.SetPreeditText(args.GetPropertyAsString("data")); @@ -342,6 +372,7 @@ namespace Avalonia.Browser if (_client == null) return false; + Console.WriteLine("composition end"); IsComposing = false; _client.SetPreeditText(null); _topLevelImpl.RawTextEvent(args.GetPropertyAsString("data")!); diff --git a/src/Browser/Avalonia.Browser/Interop/InputHelper.cs b/src/Browser/Avalonia.Browser/Interop/InputHelper.cs index 7a010dc782..a816e39da8 100644 --- a/src/Browser/Avalonia.Browser/Interop/InputHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/InputHelper.cs @@ -18,6 +18,8 @@ internal static partial class InputHelper [JSImport("InputHelper.subscribeTextEvents", AvaloniaModule.MainModuleName)] public static partial void SubscribeTextEvents( JSObject htmlElement, + [JSMarshalAs>] + Func onBeforeInput, [JSMarshalAs>] Func onInput, [JSMarshalAs>] diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/input.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/input.ts index 83e8ee7f1c..0f0e5eb512 100644 --- a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/input.ts +++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/input.ts @@ -47,6 +47,7 @@ export class InputHelper { public static subscribeTextEvents( element: HTMLInputElement, + beforeInputCallback: (args: InputEvent, start: number, end: number) => boolean, inputCallback: (type: string, data: string | null) => boolean, compositionStartCallback: (args: CompositionEvent) => boolean, compositionUpdateCallback: (args: CompositionEvent) => boolean, @@ -68,6 +69,25 @@ export class InputHelper { }; element.addEventListener("compositionstart", compositionStartHandler); + const beforeInputHandler = (args: InputEvent) => { + const ranges = args.getTargetRanges(); + let start = -1; + let end = -1; + if (ranges.length > 0) { + start = ranges[0].startOffset; + end = ranges[0].endOffset; + } + + if (args.inputType === "insertCompositionText") { + start = 2; + end = start + 2; + } + if (beforeInputCallback(args, start, end)) { + args.preventDefault(); + } + }; + element.addEventListener("beforeinput", beforeInputHandler); + const compositionUpdateHandler = (args: CompositionEvent) => { if (compositionUpdateCallback(args)) { args.preventDefault(); From ff983442470f5f851da9eebc8e83b1bf14393ad9 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 13 Feb 2023 08:39:34 +0000 Subject: [PATCH 29/61] allow multiline input on android --- samples/MobileSandbox/MainView.xaml | 4 ++-- src/Android/Avalonia.Android/AndroidInputMethod.cs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/samples/MobileSandbox/MainView.xaml b/samples/MobileSandbox/MainView.xaml index 1eab13aa75..5d35ec3fec 100644 --- a/samples/MobileSandbox/MainView.xaml +++ b/samples/MobileSandbox/MainView.xaml @@ -5,8 +5,8 @@ x:DataType="mobileSandbox:MainView"> - - + +