From be5c11e5236fb8840ab5d0514f7588f9099c394b Mon Sep 17 00:00:00 2001 From: xiaoy312 Date: Tue, 7 Jan 2025 18:00:24 -0500 Subject: [PATCH] fix(zcc): add support for negative canvas coord space --- .../ZoomContentControl/ZoomContentControl.cs | 104 +++++++++++------- .../Extensions/RectExtensions.cs | 15 +++ 2 files changed, 82 insertions(+), 37 deletions(-) diff --git a/src/Uno.Toolkit.UI/Controls/ZoomContentControl/ZoomContentControl.cs b/src/Uno.Toolkit.UI/Controls/ZoomContentControl/ZoomContentControl.cs index 064627e0b..c42c47952 100644 --- a/src/Uno.Toolkit.UI/Controls/ZoomContentControl/ZoomContentControl.cs +++ b/src/Uno.Toolkit.UI/Controls/ZoomContentControl/ZoomContentControl.cs @@ -89,6 +89,7 @@ T FindTemplatePart(string name) where T : class => _translation = FindTemplatePart(TemplateParts.TranslateTransform); ResetViewport(); + UpdateScrollDetails(); } protected override void OnContentChanged(object oldContent, object newContent) @@ -98,6 +99,8 @@ protected override void OnContentChanged(object oldContent, object newContent) { fe.Loaded += OnContentLoaded; fe.SizeChanged += OnContentSizeChanged; + UpdateScrollDetails(); + _contentSubscriptions.Disposable = Disposable.Create(() => { fe.Loaded -= OnContentLoaded; @@ -107,6 +110,10 @@ protected override void OnContentChanged(object oldContent, object newContent) void OnContentLoaded(object sender, RoutedEventArgs e) { + if (Content is Canvas) + { + UpdateScrollDetails(); + } if (AutoFitToCanvas) { FitToCanvas(); @@ -114,12 +121,7 @@ void OnContentLoaded(object sender, RoutedEventArgs e) } void OnContentSizeChanged(object sender, SizeChangedEventArgs e) { - _contentSize = new Size(fe.ActualWidth, fe.ActualHeight); - HorizontalZoomCenter = _contentSize.Width / 2; - VerticalZoomCenter = _contentSize.Height / 2; - - UpdateScrollBars(); - UpdateScrollVisibility(); + UpdateScrollDetails(); } } @@ -152,28 +154,19 @@ private async void OnZoomLevelChanged() } UpdateScrollBars(); - UpdateScrollVisibility(); await RaiseRenderedContentUpdated(); } - private void UpdateScrollVisibility() + private async void UpdateScrollDetails() { - if (Viewport is { } vp) - { - ToggleScrollBarVisibility(_scrollH, vp.ActualWidth < ScrollExtentWidth); - ToggleScrollBarVisibility(_scrollV, vp.ActualHeight < ScrollExtentHeight); - } - - void ToggleScrollBarVisibility(ScrollBar? sb, bool value) + if (Content is FrameworkElement fe) { - if (sb is null) return; - - // Showing/hiding the ScrollBar(s)could cause the ContentPresenter to move as it re-centers. - // This adds unnecessary complexity for the zooming logics as we need to preserve the focal point - // under the cursor position or the pinch center point after zooming. - // To avoid all that, we just make them permanently there for layout calculation. - sb.IsEnabled = value; - sb.Opacity = value ? 1 : 0; + _contentSize = new Size(fe.ActualWidth, fe.ActualHeight); + HorizontalZoomCenter = _contentSize.Width / 2; + VerticalZoomCenter = _contentSize.Height / 2; + + UpdateScrollBars(); + await RaiseRenderedContentUpdated(); } } @@ -207,20 +200,57 @@ private void UpdateScrollBars() { if (Viewport is { } vp) { - var scrollableWidth = Math.Max(0, ScrollExtentWidth - vp.ActualWidth); - var scrollableHeight = Math.Max(0, ScrollExtentHeight - vp.ActualHeight); - - // since the content is always centered, we need to able to scroll both way equally: - // [Content-Content-Content] - // [=======[Viewport]======] - HorizontalMaxScroll = scrollableWidth / 2; - HorizontalMinScroll = -HorizontalMaxScroll; - VerticalMaxScroll = scrollableHeight / 2; - VerticalMinScroll = -VerticalMaxScroll; - - // update size of thumb - if (_scrollH is { }) _scrollH.ViewportSize = vp.ActualWidth; - if (_scrollV is { }) _scrollV.ViewportSize = vp.ActualHeight; + if (Content is Canvas { Children: { Count: > 0 } } canvas) + { + var realm = canvas.Children + .Select(x => new Rect(x.ActualOffset.X, x.ActualOffset.Y, x.ActualSize.X, x.ActualSize.Y)) + .Aggregate(RectHelper.Union); + if (realm != default) + { + realm = realm.Multiply(ZoomLevel).Inflate(AdditionalMargin); + + HorizontalMinScroll = -realm.Right + (vp.ActualWidth / 2); + HorizontalMaxScroll = -realm.Left - (vp.ActualWidth / 2); + + VerticalMinScroll = realm.Top + (vp.ActualHeight / 2); + VerticalMaxScroll = realm.Bottom - (vp.ActualHeight / 2); + } + else + { + HorizontalMaxScroll = HorizontalMinScroll = 0; + VerticalMaxScroll = VerticalMinScroll = 0; + } + } + else + { + var scrollableWidth = Math.Max(0, ScrollExtentWidth - vp.ActualWidth); + var scrollableHeight = Math.Max(0, ScrollExtentHeight - vp.ActualHeight); + + // since the content is always centered, we need to able to scroll both way equally: + // [Content-Content-Content] + // [=======[Viewport]======] + HorizontalMaxScroll = scrollableWidth / 2; + HorizontalMinScroll = -HorizontalMaxScroll; + VerticalMaxScroll = scrollableHeight / 2; + VerticalMinScroll = -VerticalMaxScroll; + } + + Update(_scrollH, HorizontalMinScroll < HorizontalMaxScroll, vp.ActualWidth); + Update(_scrollV, VerticalMinScroll < VerticalMaxScroll, vp.ActualHeight); + void Update(ScrollBar? sb, bool shown, double thumbSize) + { + if (sb is null) return; + + // update size of thumb + sb.ViewportSize = thumbSize; + + // Showing/hiding the ScrollBar(s)could cause the ContentPresenter to move as it re-centers. + // This adds unnecessary complexity for the zooming logics as we need to preserve the focal point + // under the cursor position or the pinch center point after zooming. + // To avoid all that, we just make them permanently there for layout calculation. + sb.IsEnabled = shown; + sb.Opacity = shown ? 1 : 0; + } } } diff --git a/src/Uno.Toolkit.UI/Extensions/RectExtensions.cs b/src/Uno.Toolkit.UI/Extensions/RectExtensions.cs index 0f8515ea5..ece1dd628 100644 --- a/src/Uno.Toolkit.UI/Extensions/RectExtensions.cs +++ b/src/Uno.Toolkit.UI/Extensions/RectExtensions.cs @@ -5,10 +5,25 @@ using System.Threading.Tasks; using Windows.Foundation; +#if IS_WINUI +using Microsoft.UI.Xaml; +#else +using Windows.UI.Xaml; +#endif + namespace Uno.Toolkit.UI { internal static class RectExtensions { public static bool IsEmptyOrZero(this Rect rect) => rect is { Width: 0, Height: 0 }; + + public static Rect Multiply(this Rect x, double value) => new Rect(x.X * value, x.Y * value, x.Width * value, x.Height * value); + + public static Rect Inflate(this Rect x, Thickness value) => new Rect( + x.X - value.Left, + x.Y - value.Top, + x.Width + (value.Left + value.Right), + x.Height + (value.Top + value.Bottom) + ); } }