diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index d4db1573ad..ce84a7fe84 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -51,6 +51,9 @@ MembersMustExist : Member 'protected void Avalonia.Controls.Presenters.TextPrese CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Primitives.PopupRoot' does not implement interface 'Avalonia.Utilities.IWeakSubscriber' in the implementation but it does in the contract. TypesMustExist : Type 'Avalonia.Platform.ExportWindowingSubsystemAttribute' does not exist in the implementation but it does exist in the contract. EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.Screen Avalonia.Platform.IScreenImpl.ScreenFromPoint(Avalonia.PixelPoint)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.Screen Avalonia.Platform.IScreenImpl.ScreenFromRect(Avalonia.PixelRect)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.Screen Avalonia.Platform.IScreenImpl.ScreenFromWindow(Avalonia.Platform.IWindowBaseImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Action Avalonia.Platform.ITopLevelImpl.Resized.get()' is present in the implementation but not in the contract. @@ -69,4 +72,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract. -Total Issues: 70 +Total Issues: 73 diff --git a/src/Avalonia.Controls/Platform/IScreenImpl.cs b/src/Avalonia.Controls/Platform/IScreenImpl.cs index 5bd45057d9..b68391aa52 100644 --- a/src/Avalonia.Controls/Platform/IScreenImpl.cs +++ b/src/Avalonia.Controls/Platform/IScreenImpl.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +#nullable enable + namespace Avalonia.Platform { public interface IScreenImpl @@ -7,5 +9,11 @@ namespace Avalonia.Platform int ScreenCount { get; } IReadOnlyList AllScreens { get; } + + Screen? ScreenFromWindow(IWindowBaseImpl window); + + Screen? ScreenFromPoint(PixelPoint point); + + Screen? ScreenFromRect(PixelRect rect); } } diff --git a/src/Avalonia.Controls/Platform/ScreenHelper.cs b/src/Avalonia.Controls/Platform/ScreenHelper.cs new file mode 100644 index 0000000000..0bd2be69d0 --- /dev/null +++ b/src/Avalonia.Controls/Platform/ScreenHelper.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using Avalonia.Utilities; + +#nullable enable + +namespace Avalonia.Platform +{ + public static class ScreenHelper + { + public static Screen? ScreenFromPoint(PixelPoint point, IReadOnlyList screens) + { + foreach (Screen screen in screens) + { + if (screen.Bounds.ContainsExclusive(point)) + { + return screen; + } + } + + return null; + } + + public static Screen? ScreenFromRect(PixelRect bounds, IReadOnlyList screens) + { + Screen? currMaxScreen = null; + double maxAreaSize = 0; + + foreach (Screen screen in screens) + { + double left = MathUtilities.Clamp(bounds.X, screen.Bounds.X, screen.Bounds.X + screen.Bounds.Width); + double top = MathUtilities.Clamp(bounds.Y, screen.Bounds.Y, screen.Bounds.Y + screen.Bounds.Height); + double right = MathUtilities.Clamp(bounds.X + bounds.Width, screen.Bounds.X, screen.Bounds.X + screen.Bounds.Width); + double bottom = MathUtilities.Clamp(bounds.Y + bounds.Height, screen.Bounds.Y, screen.Bounds.Y + screen.Bounds.Height); + double area = (right - left) * (bottom - top); + if (area > maxAreaSize) + { + maxAreaSize = area; + currMaxScreen = screen; + } + } + + return currMaxScreen; + } + + public static Screen? ScreenFromWindow(IWindowBaseImpl window, IReadOnlyList screens) + { + var rect = new PixelRect( + window.Position, + PixelSize.FromSize(window.FrameSize ?? window.ClientSize, window.DesktopScaling)); + + return ScreenFromRect(rect, screens); + } + } +} diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs index 0f0dd7311d..a80a60350e 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs @@ -106,9 +106,9 @@ namespace Avalonia.Controls.Primitives.PopupPositioning { var screens = _popup.Screens; - var targetScreen = screens.FirstOrDefault(s => s.Bounds.Contains(anchorRect.TopLeft)) + var targetScreen = screens.FirstOrDefault(s => s.Bounds.ContainsExclusive(anchorRect.TopLeft)) ?? screens.FirstOrDefault(s => s.Bounds.Intersects(anchorRect)) - ?? screens.FirstOrDefault(s => s.Bounds.Contains(parentGeometry.TopLeft)) + ?? screens.FirstOrDefault(s => s.Bounds.ContainsExclusive(parentGeometry.TopLeft)) ?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry)) ?? screens.FirstOrDefault(); diff --git a/src/Avalonia.Controls/Screens.cs b/src/Avalonia.Controls/Screens.cs index ef438576a7..a554f82f61 100644 --- a/src/Avalonia.Controls/Screens.cs +++ b/src/Avalonia.Controls/Screens.cs @@ -2,54 +2,45 @@ using System.Collections.Generic; using System.Linq; using Avalonia.Platform; -using Avalonia.Utilities; using Avalonia.VisualTree; +#nullable enable + namespace Avalonia.Controls { public class Screens { - private readonly IScreenImpl? _iScreenImpl; + private readonly IScreenImpl _iScreenImpl; public int ScreenCount => _iScreenImpl?.ScreenCount ?? 0; public IReadOnlyList All => _iScreenImpl?.AllScreens ?? Array.Empty(); public Screen? Primary => All.FirstOrDefault(x => x.Primary); - public Screens(IScreenImpl? iScreenImpl) + public Screens(IScreenImpl iScreenImpl) { _iScreenImpl = iScreenImpl; } - public Screen? ScreenFromBounds(PixelRect bounds){ - - Screen? currMaxScreen = null; - double maxAreaSize = 0; - foreach (Screen screen in All) - { - double left = MathUtilities.Clamp(bounds.X, screen.Bounds.X, screen.Bounds.X + screen.Bounds.Width); - double top = MathUtilities.Clamp(bounds.Y, screen.Bounds.Y, screen.Bounds.Y + screen.Bounds.Height); - double right = MathUtilities.Clamp(bounds.X + bounds.Width, screen.Bounds.X, screen.Bounds.X + screen.Bounds.Width); - double bottom = MathUtilities.Clamp(bounds.Y + bounds.Height, screen.Bounds.Y, screen.Bounds.Y + screen.Bounds.Height); - double area = (right - left) * (bottom - top); - if (area > maxAreaSize) - { - maxAreaSize = area; - currMaxScreen = screen; - } - } - - return currMaxScreen; + public Screen? ScreenFromBounds(PixelRect bounds) + { + return _iScreenImpl.ScreenFromRect(bounds); } - public Screen? ScreenFromPoint(PixelPoint point) + public Screen? ScreenFromWindow(IWindowBaseImpl window) { - return All.FirstOrDefault(x => x.Bounds.Contains(point)); + return _iScreenImpl.ScreenFromWindow(window); + } + + public Screen? ScreenFromPoint(PixelPoint point) + { + return _iScreenImpl.ScreenFromPoint(point); } public Screen? ScreenFromVisual(IVisual visual) { var tl = visual.PointToScreen(visual.Bounds.TopLeft); var br = visual.PointToScreen(visual.Bounds.BottomRight); + return ScreenFromBounds(new PixelRect(tl, br)); } } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index eda23389f7..feacc3e63a 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -879,7 +879,19 @@ namespace Avalonia.Controls if (startupLocation == WindowStartupLocation.CenterScreen) { - var screen = Screens.ScreenFromPoint(owner?.Position ?? Position); + Screen? screen = null; + + if (owner is not null) + { + screen = Screens.ScreenFromWindow(owner); + + screen ??= Screens.ScreenFromPoint(owner.Position); + } + + if (screen is null) + { + screen = Screens.ScreenFromPoint(Position); + } if (screen != null) { diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index d3c940c07c..12ba143c8a 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -60,7 +60,7 @@ namespace Avalonia.Controls public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver? dependencyResolver) : base(impl, dependencyResolver) { - Screens = new Screens(PlatformImpl?.Screen); + Screens = new Screens(impl.Screen); impl.Activated = HandleActivated; impl.Deactivated = HandleDeactivated; impl.PositionChanged = HandlePositionChanged; diff --git a/src/Avalonia.Controls/WindowTransparencyLevel.cs b/src/Avalonia.Controls/WindowTransparencyLevel.cs index f416b5de91..d463f74a0e 100644 --- a/src/Avalonia.Controls/WindowTransparencyLevel.cs +++ b/src/Avalonia.Controls/WindowTransparencyLevel.cs @@ -22,6 +22,11 @@ /// AcrylicBlur, + /// + /// Force acrylic on some incompatible versions of Windows 10. + /// + ForceAcrylicBlur, + /// /// The window background is based on desktop wallpaper tint with a blur. This will only work on Windows 11 /// diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 8fb2c456b2..a5f478f445 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -237,5 +237,20 @@ namespace Avalonia.DesignerSupport.Remote public IReadOnlyList AllScreens { get; } = new Screen[] { new Screen(1, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) }; + + public Screen ScreenFromPoint(PixelPoint point) + { + return ScreenHelper.ScreenFromPoint(point, AllScreens); + } + + public Screen ScreenFromRect(PixelRect rect) + { + return ScreenHelper.ScreenFromRect(rect, AllScreens); + } + + public Screen ScreenFromWindow(IWindowBaseImpl window) + { + return ScreenHelper.ScreenFromWindow(window, AllScreens); + } } } diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index 9ac74e5e78..f22fb4f317 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -200,5 +200,20 @@ namespace Avalonia.Headless new Screen(1, new PixelRect(0, 0, 1920, 1280), new PixelRect(0, 0, 1920, 1280), true), }; + + public Screen ScreenFromPoint(PixelPoint point) + { + return ScreenHelper.ScreenFromPoint(point, AllScreens); + } + + public Screen ScreenFromRect(PixelRect rect) + { + return ScreenHelper.ScreenFromRect(rect, AllScreens); + } + + public Screen ScreenFromWindow(IWindowBaseImpl window) + { + return ScreenHelper.ScreenFromWindow(window, AllScreens); + } } } diff --git a/src/Avalonia.Native/ScreenImpl.cs b/src/Avalonia.Native/ScreenImpl.cs index 7b4a001486..83db2e8a28 100644 --- a/src/Avalonia.Native/ScreenImpl.cs +++ b/src/Avalonia.Native/ScreenImpl.cs @@ -48,5 +48,20 @@ namespace Avalonia.Native _native?.Dispose(); _native = null; } + + public Screen ScreenFromPoint(PixelPoint point) + { + return ScreenHelper.ScreenFromPoint(point, AllScreens); + } + + public Screen ScreenFromRect(PixelRect rect) + { + return ScreenHelper.ScreenFromRect(rect, AllScreens); + } + + public Screen ScreenFromWindow(IWindowBaseImpl window) + { + return ScreenHelper.ScreenFromWindow(window, AllScreens); + } } } diff --git a/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml index e5b524beeb..c82757799d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml @@ -121,7 +121,7 @@ - diff --git a/src/Avalonia.Visuals/Media/PixelRect.cs b/src/Avalonia.Visuals/Media/PixelRect.cs index 0059a4b483..855ba104ad 100644 --- a/src/Avalonia.Visuals/Media/PixelRect.cs +++ b/src/Avalonia.Visuals/Media/PixelRect.cs @@ -168,6 +168,18 @@ namespace Avalonia { return p.X >= X && p.X <= Right && p.Y >= Y && p.Y <= Bottom; } + + /// + /// Determines whether a point is in the bounds of the rectangle, exclusive of the + /// rectangle's bottom/right edge. + /// + /// The point. + /// true if the point is in the bounds of the rectangle; otherwise false. + public bool ContainsExclusive(PixelPoint p) + { + return p.X >= X && p.X < X + Width && + p.Y >= Y && p.Y < Y + Height; + } /// /// Determines whether the rectangle fully contains another rectangle. diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 6d7d6c2e54..7930228d99 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -252,6 +252,18 @@ namespace Avalonia return p.X >= _x && p.X <= _x + _width && p.Y >= _y && p.Y <= _y + _height; } + + /// + /// Determines whether a point is in the bounds of the rectangle, exclusive of the + /// rectangle's bottom/right edge. + /// + /// The point. + /// true if the point is in the bounds of the rectangle; otherwise false. + public bool ContainsExclusive(Point p) + { + return p.X >= _x && p.X < _x + _width && + p.Y >= _y && p.Y < _y + _height; + } /// /// Determines whether the rectangle fully contains another rectangle. diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs index bf5c74e0e5..bcaafb6a53 100644 --- a/src/Avalonia.X11/X11Screens.cs +++ b/src/Avalonia.X11/X11Screens.cs @@ -200,6 +200,21 @@ namespace Avalonia.X11 } + public Screen ScreenFromPoint(PixelPoint point) + { + return ScreenHelper.ScreenFromPoint(point, AllScreens); + } + + public Screen ScreenFromRect(PixelRect rect) + { + return ScreenHelper.ScreenFromRect(rect, AllScreens); + } + + public Screen ScreenFromWindow(IWindowBaseImpl window) + { + return ScreenHelper.ScreenFromWindow(window, AllScreens); + } + public int ScreenCount => _impl.Screens.Length; public IReadOnlyList AllScreens => diff --git a/src/Web/Avalonia.Web.Blazor/WinStubs.cs b/src/Web/Avalonia.Web.Blazor/WinStubs.cs index 7b2bff6bfd..7c30a96d35 100644 --- a/src/Web/Avalonia.Web.Blazor/WinStubs.cs +++ b/src/Web/Avalonia.Web.Blazor/WinStubs.cs @@ -55,5 +55,20 @@ namespace Avalonia.Web.Blazor public IReadOnlyList AllScreens { get; } = new[] { new Screen(96, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) }; + + public Screen? ScreenFromPoint(PixelPoint point) + { + return ScreenHelper.ScreenFromPoint(point, AllScreens); + } + + public Screen? ScreenFromRect(PixelRect rect) + { + return ScreenHelper.ScreenFromRect(rect, AllScreens); + } + + public Screen? ScreenFromWindow(IWindowBaseImpl window) + { + return ScreenHelper.ScreenFromWindow(window, AllScreens); + } } } diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 442794f0f0..96e45927da 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Avalonia.Platform; using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; @@ -70,5 +71,43 @@ namespace Avalonia.Win32 { _allScreens = null; } + + public Screen ScreenFromWindow(IWindowBaseImpl window) + { + var handle = window.Handle.Handle; + + var monitor = MonitorFromWindow(handle, MONITOR.MONITOR_DEFAULTTONULL); + + return FindScreenByHandle(monitor); + } + + public Screen ScreenFromPoint(PixelPoint point) + { + var monitor = MonitorFromPoint(new POINT + { + X = point.X, + Y = point.Y + }, MONITOR.MONITOR_DEFAULTTONULL); + + return FindScreenByHandle(monitor); + } + + public Screen ScreenFromRect(PixelRect rect) + { + var monitor = MonitorFromRect(new RECT + { + left = rect.TopLeft.X, + top = rect.TopLeft.Y, + right = rect.TopRight.X, + bottom = rect.BottomRight.Y + }, MONITOR.MONITOR_DEFAULTTONULL); + + return FindScreenByHandle(monitor); + } + + private Screen FindScreenByHandle(IntPtr handle) + { + return AllScreens.Cast().FirstOrDefault(m => m.Handle == handle); + } } } diff --git a/src/Windows/Avalonia.Win32/WinScreen.cs b/src/Windows/Avalonia.Win32/WinScreen.cs index 0cf9fe31db..f103cc3b66 100644 --- a/src/Windows/Avalonia.Win32/WinScreen.cs +++ b/src/Windows/Avalonia.Win32/WinScreen.cs @@ -9,9 +9,11 @@ namespace Avalonia.Win32 public WinScreen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary, IntPtr hMonitor) : base(pixelDensity, bounds, workingArea, primary) { - this._hMonitor = hMonitor; + _hMonitor = hMonitor; } + public IntPtr Handle => _hMonitor; + public override int GetHashCode() { return (int)_hMonitor; @@ -19,7 +21,7 @@ namespace Avalonia.Win32 public override bool Equals(object obj) { - return (obj is WinScreen screen) ? this._hMonitor == screen._hMonitor : base.Equals(obj); + return (obj is WinScreen screen) ? _hMonitor == screen._hMonitor : base.Equals(obj); } } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 3953a0995d..41269ee7ca 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -396,6 +396,11 @@ namespace Avalonia.Win32 } } + if (Win32Platform.WindowsVersion < WinUICompositorConnection.MinHostBackdropVersion && effect == BlurEffect.Mica) + { + effect = BlurEffect.Acrylic; + } + _blurHost?.SetBlur(effect); return transparencyLevel; @@ -428,7 +433,8 @@ namespace Avalonia.Win32 break; case WindowTransparencyLevel.AcrylicBlur: - case (WindowTransparencyLevel.AcrylicBlur + 1): // hack-force acrylic. + case WindowTransparencyLevel.ForceAcrylicBlur: // hack-force acrylic. + case WindowTransparencyLevel.Mica: accent.AccentState = AccentState.ACCENT_ENABLE_ACRYLIC; transparencyLevel = WindowTransparencyLevel.AcrylicBlur; break; diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index eb128ef038..f643a84e37 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -516,6 +516,8 @@ namespace Avalonia.Controls.UnitTests var screens = new Mock(); screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object, screen2.Object }); + screens.Setup(x => x.ScreenFromPoint(It.IsAny())).Returns(screen1.Object); + var windowImpl = MockWindowingPlatform.CreateWindowMock(); windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480));