diff --git a/src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewBaseToolbar.cs b/src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewBaseToolbar.cs index 77288cbb6..f8f2cae3b 100644 --- a/src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewBaseToolbar.cs +++ b/src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewBaseToolbar.cs @@ -2,6 +2,8 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; +using Avalonia.LogicalTree; +using Avalonia.VisualTree; namespace AtomUI.Desktop.Controls; @@ -300,7 +302,7 @@ internal class ImagePreviewBaseToolbar : TemplatedControl } else if (sender == _fitToWindowButton) { - RaiseEvent(new ImageFitToWindowEventArgs(_fitToWindowButton?.IsChecked == true) + RaiseToolbarEvent(new ImageFitToWindowEventArgs(_fitToWindowButton?.IsChecked == true) { RoutedEvent = FitToWindowRequestEvent }); @@ -309,9 +311,33 @@ internal class ImagePreviewBaseToolbar : TemplatedControl private void RaiseRequestEvent(RoutedEvent routedEvent) { - RaiseEvent(new ImagePreviewToolbarRequestEventArgs(ToolbarSource) + RaiseToolbarEvent(new ImagePreviewToolbarRequestEventArgs(ToolbarSource) { RoutedEvent = routedEvent }); } + + private void RaiseToolbarEvent(RoutedEventArgs args) + { + if (ResolveCsdTitleBarHostWindow() is { } hostWindow) + { + hostWindow.RaiseRoutedEventFromOverlay(this, args); + return; + } + + RaiseEvent(args); + } + + private Window? ResolveCsdTitleBarHostWindow() + { + if (ToolbarSource != ImagePreviewToolbarSource.TitleBar) + { + return null; + } + + var titleBar = this.FindLogicalAncestorOfType() ?? + this.FindAncestorOfType(); + var hostWindow = titleBar?.HostWindow; + return hostWindow is { IsCsdEnabled: true } ? hostWindow : null; + } } diff --git a/tests/AtomUI.Desktop.Controls.Tests/Dialog/WindowDialogPresenterTests.cs b/tests/AtomUI.Desktop.Controls.Tests/Dialog/WindowDialogPresenterTests.cs index ea9496dc0..ad667ad62 100644 --- a/tests/AtomUI.Desktop.Controls.Tests/Dialog/WindowDialogPresenterTests.cs +++ b/tests/AtomUI.Desktop.Controls.Tests/Dialog/WindowDialogPresenterTests.cs @@ -904,100 +904,6 @@ public class WindowDialogPresenterTests } } - [Fact] - public void Native_User_Client_Size_Changes_Update_The_Window_Dialog_Normal_Size() - { - var owner = new AtomUI.Desktop.Controls.Window - { - Width = 800, - Height = 600 - }; - var dialog = new AtomUI.Desktop.Controls.Dialog - { - IsModal = false, - IsMotionEnabled = false, - IsResizable = true, - HostWidth = 360, - HostHeight = 220 - }; - var presenter = new WindowDialogPresenter(dialog, owner); - - try - { - owner.Show(); - WaitWithDispatcherPump(presenter.ShowAsync(CancellationToken.None).AsTask()); - var surface = GetSurface(presenter); - var chrome = GetWindowChromeSize(presenter.HostWindow); - var platformSurfaceSize = new Size(500, 280); - - presenter.HostWindow.BeginNativeUserResize(); - presenter.HostWindow.SetPlatformChromeClientSize(platformSurfaceSize + chrome); - Dispatcher.UIThread.RunJobs(); - presenter.HostWindow.CompleteNativeUserResize(); - Dispatcher.UIThread.RunJobs(); - - surface.Bounds.Size.ShouldBe(platformSurfaceSize); - - presenter.HostWindow.Padding = new Thickness(3, 4, 5, 6); - Dispatcher.UIThread.RunJobs(); - - var updatedChrome = GetWindowChromeSize(presenter.HostWindow); - surface.Bounds.Size.ShouldBe(platformSurfaceSize); - presenter.HostWindow.ClientSize.ShouldBe(platformSurfaceSize + updatedChrome); - } - finally - { - WaitWithDispatcherPump(presenter.CloseAsync().AsTask()); - WaitWithDispatcherPump(presenter.DisposeAsync().AsTask()); - owner.Close(); - } - } - - [Fact] - public void Sizing_Refresh_Does_Not_Treat_Stale_Client_Size_As_User_Resize() - { - var owner = new AtomUI.Desktop.Controls.Window - { - Width = 800, - Height = 600 - }; - var dialog = new AtomUI.Desktop.Controls.Dialog - { - IsModal = false, - IsMotionEnabled = false, - IsResizable = true, - HostWidth = 360, - HostHeight = 220 - }; - var presenter = new WindowDialogPresenter(dialog, owner); - - try - { - owner.Show(); - WaitWithDispatcherPump(presenter.ShowAsync(CancellationToken.None).AsTask()); - var surface = GetSurface(presenter); - var normalSurfaceSize = surface.Bounds.Size; - var chrome = GetWindowChromeSize(presenter.HostWindow); - var staleSurfaceSize = normalSurfaceSize + new Size(120, 80); - - presenter.HostWindow.SetPlatformChromeClientSize(staleSurfaceSize + chrome); - Dispatcher.UIThread.RunJobs(); - - presenter.HostWindow.Padding = new Thickness(3, 4, 5, 6); - Dispatcher.UIThread.RunJobs(); - - var updatedChrome = GetWindowChromeSize(presenter.HostWindow); - surface.Bounds.Size.ShouldBe(normalSurfaceSize); - presenter.HostWindow.ClientSize.ShouldBe(normalSurfaceSize + updatedChrome); - } - finally - { - WaitWithDispatcherPump(presenter.CloseAsync().AsTask()); - WaitWithDispatcherPump(presenter.DisposeAsync().AsTask()); - owner.Close(); - } - } - [Fact] public void Csd_Constraints_Ignore_The_Managed_TitleBar_Minimum_Width() { diff --git a/tests/AtomUI.Desktop.Controls.Tests/ImagePreviewer/ImagePreviewerTitleTests.cs b/tests/AtomUI.Desktop.Controls.Tests/ImagePreviewer/ImagePreviewerTitleTests.cs index 9dc180150..da3160db8 100644 --- a/tests/AtomUI.Desktop.Controls.Tests/ImagePreviewer/ImagePreviewerTitleTests.cs +++ b/tests/AtomUI.Desktop.Controls.Tests/ImagePreviewer/ImagePreviewerTitleTests.cs @@ -183,6 +183,73 @@ public class ImagePreviewerTitleTests }); } + [Fact] + public void ImagePreviewerDialog_Handles_TitleBar_Toolbar_Requests_Relayed_From_Csd_Overlay() + { + Dispatcher.UIThread.Invoke(() => + { + var dialog = new TestImagePreviewerDialog + { + ItemsSource = + [ + new ImagePreviewItem(new UriImagePreviewSource("avares://AtomUI.Tests/Assets/first.png")), + new ImagePreviewItem(new UriImagePreviewSource("avares://AtomUI.Tests/Assets/second.png")) + ], + CurrentImage = LoadedImageSource.CreateSvg("", new Avalonia.Size(10, 10)), + ImageScaleX = 1.0, + ImageScaleY = 1.0 + }; + var titleBar = new ImagePreviewerTitleBar(); + + dialog.ConfigureTitleBar(titleBar); + var toolbar = titleBar.LeftAddOn.ShouldNotBeNull() + .ShouldBeAssignableTo(); + + var rotateArgs = RelayToolbarRequest(dialog, toolbar, ImagePreviewBaseToolbar.RotateRightRequestEvent); + rotateArgs.Handled.ShouldBeTrue(); + dialog.ImageRotate.ShouldBe(Math.PI / 2, 0.000001); + + var flipArgs = RelayToolbarRequest(dialog, toolbar, ImagePreviewBaseToolbar.HorizontalFlipRequestEvent); + flipArgs.Handled.ShouldBeTrue(); + dialog.ImageScaleX.ShouldBe(-1.0); + + var fitArgs = new ImageFitToWindowEventArgs(false) + { + RoutedEvent = ImagePreviewBaseToolbar.FitToWindowRequestEvent + }; + dialog.RaiseRoutedEventFromOverlay(toolbar, fitArgs); + + fitArgs.Handled.ShouldBeTrue(); + dialog.IsImageFitToWindow.ShouldBeFalse(); + + var nextArgs = RelayToolbarRequest(dialog, toolbar, ImagePreviewBaseToolbar.NextRequestEvent); + nextArgs.Handled.ShouldBeTrue(); + dialog.CurrentIndex.ShouldBe(1); + }); + } + + [Fact] + public void ImagePreviewToolbar_TitleBar_Requests_Use_Host_Window_Route_In_Csd() + { + var toolbarSource = File.ReadAllText(GetRepoFile( + "src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewBaseToolbar.cs")); + var titleBarSource = File.ReadAllText(GetRepoFile( + "src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewerTitleBar.cs")); + var dialogSource = File.ReadAllText(GetRepoFile( + "src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewerDialog.cs")); + + toolbarSource.ShouldContain("ResolveCsdTitleBarHostWindow()"); + toolbarSource.ShouldContain("ToolbarSource != ImagePreviewToolbarSource.TitleBar"); + toolbarSource.ShouldContain("hostWindow.RaiseRoutedEventFromOverlay(this, args);"); + toolbarSource.ShouldContain("RaiseEvent(args);"); + toolbarSource.IndexOf("hostWindow.RaiseRoutedEventFromOverlay(this, args);", StringComparison.Ordinal) + .ShouldBeLessThan(toolbarSource.IndexOf("RaiseEvent(args);", StringComparison.Ordinal)); + + titleBarSource.ShouldNotContain("ToolbarHorizontalFlipRequest"); + dialogSource.ShouldNotContain("_titleBarRequestSource"); + dialogSource.ShouldContain("ImagePreviewBaseToolbar.NextRequestEvent.AddClassHandler"); + } + [Fact] public void ImagePreviewerOverlayHost_ItemsSource_Change_Preserves_CurrentIndex() { @@ -268,6 +335,19 @@ public class ImagePreviewerTitleTests throw new FileNotFoundException($"Could not find repository file: {relativePath}"); } + private static ImagePreviewToolbarRequestEventArgs RelayToolbarRequest( + ImagePreviewerDialog dialog, + ImagePreviewToolbar toolbar, + Avalonia.Interactivity.RoutedEvent routedEvent) + { + var args = new ImagePreviewToolbarRequestEventArgs(ImagePreviewToolbarSource.TitleBar) + { + RoutedEvent = routedEvent + }; + dialog.RaiseRoutedEventFromOverlay(toolbar, args); + return args; + } + private sealed class PrefixTitleResolver : IImagePreviewTitleResolver { private readonly string _prefix;