diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/Primitives/CalendarPanel.ModernCollectionBasePanel.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/Primitives/CalendarPanel.ModernCollectionBasePanel.cs index 45e47a96864a..2e783a290e67 100644 --- a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/Primitives/CalendarPanel.ModernCollectionBasePanel.cs +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/Primitives/CalendarPanel.ModernCollectionBasePanel.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using Windows.Foundation; +using Windows.UI.Core; using Uno; using Uno.Extensions; using Uno.Extensions.Specialized; @@ -386,7 +387,8 @@ internal void ScrollItemIntoView(int index, ScrollIntoViewAlignment alignment, d sv.ChangeView( horizontalOffset: null, verticalOffset: newOffset, - zoomFactor: null); + zoomFactor: null, + forceSynchronous); // Makes sure the container of the requested date is materialized before the end of this method base_MeasureOverride(_lastLayoutedViewport.Size); @@ -599,6 +601,7 @@ private Size base_MeasureOverride(Size availableSize) #endif _layoutStrategy.EndMeasure(); } + VisibleIndicesUpdated?.Invoke(this, null); _layoutStrategy.EstimatePanelExtent( @@ -655,7 +658,7 @@ private static void OnEffectiveViewportChanged(FrameworkElement sender, Effectiv // (We bypass the SetItemMinimumSize in the CalendarPanel_Partial.MeasureOverride if m_type is **not** CalendarPanelType.Primary) that._layoutStrategy.SetViewportSize(that.GetLayoutViewport().Size, out var needsMeasure); - if (needsMeasure || Math.Abs(that._effectiveViewport.Y - that._lastLayoutedViewport.Y) > 100) + if (needsMeasure || Math.Abs(that._effectiveViewport.Y - that._lastLayoutedViewport.Y) > (that._lastLayoutedViewport.Height / that.Rows) * .75) { that.InvalidateMeasure(); } diff --git a/src/Uno.UI/UI/Xaml/Controls/ScrollContentPresenter/ScrollContentPresenter.wasm.cs b/src/Uno.UI/UI/Xaml/Controls/ScrollContentPresenter/ScrollContentPresenter.wasm.cs index 69f5a6d346d9..c9847b132d24 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ScrollContentPresenter/ScrollContentPresenter.wasm.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ScrollContentPresenter/ScrollContentPresenter.wasm.cs @@ -12,6 +12,7 @@ using Windows.Foundation; using Uno.UI.Xaml; using Microsoft.Extensions.Logging; +using Uno.UI.Extensions; namespace Windows.UI.Xaml.Controls { @@ -21,6 +22,9 @@ public partial class ScrollContentPresenter : ContentPresenter, IScrollContentPr private ScrollBarVisibility _horizontalScrollBarVisibility; private bool _eventsRegistered; + private (double? horizontal, double? vertical)? _pendingScrollTo; + private FrameworkElement _rootEltUsedToProcessScrollTo; + internal Size ScrollBarSize { get @@ -179,25 +183,71 @@ private protected override void OnUnloaded() { base.OnUnloaded(); UnregisterEventHandler("scroll", (EventHandler)OnScroll, GenericEventHandlers.RaiseEventHandler); + + if (_rootEltUsedToProcessScrollTo is {} rootElt) + { + rootElt.LayoutUpdated -= TryProcessScrollTo; + _rootEltUsedToProcessScrollTo = null; + } } - public void ScrollTo(double? horizontalOffset, double? verticalOffset, bool disableAnimation) - => WindowManagerInterop.ScrollTo(HtmlId, horizontalOffset, verticalOffset, disableAnimation); + /// + internal override void OnLayoutUpdated() + { + base.OnLayoutUpdated(); - private void OnScroll(object sender, EventArgs args) + TryProcessScrollTo(); + } + + public void ScrollTo(double? horizontalOffset, double? verticalOffset, bool disableAnimation) { - var left = GetProperty("scrollLeft"); - var top = GetProperty("scrollTop"); + _pendingScrollTo = (horizontalOffset, verticalOffset); - if (!double.TryParse(left, NumberStyles.Number, CultureInfo.InvariantCulture, out var horizontalOffset)) + WindowManagerInterop.ScrollTo(HtmlId, horizontalOffset, verticalOffset, disableAnimation); + + if (_pendingScrollTo.HasValue) { - horizontalOffset = 0; + // The scroll to was not processed by the native SCP, we need to re-request ScrollTo a bit later. + // This happen has soon as the native SCP element is not in a valid state (like un-arranged or hidden). + + if (_rootEltUsedToProcessScrollTo is null && Window.Current.RootElement is FrameworkElement rootFwElt) + { + _rootEltUsedToProcessScrollTo = rootFwElt; + rootFwElt.LayoutUpdated += TryProcessScrollTo; + } + + // As the native ScrollTo is going to be async, we manually raise the event with the provided values. + // If those values are invalid, the browser will raise the final event anyway. + (TemplatedParent as ScrollViewer)?.OnScrollInternal( + horizontalOffset ?? GetNativeHorizontalOffset(), + verticalOffset ?? GetNativeVerticalOffset(), + isIntermediate: false + ); } - if (!double.TryParse(top, NumberStyles.Number, CultureInfo.InvariantCulture, out var verticalOffset)) + } + + private void TryProcessScrollTo(object sender, object e) + => TryProcessScrollTo(); + + private void TryProcessScrollTo() + { + if (_pendingScrollTo is { } scrollTo) + { + WindowManagerInterop.ScrollTo(HtmlId, scrollTo.horizontal, scrollTo.vertical, disableAnimation: true); + } + } + + private void OnScroll(object sender, EventArgs args) + { + if (IsArrangeDirty && _pendingScrollTo.HasValue) { - verticalOffset = 0; + // When the native element of the SCP is becoming "valid" with a non 0 offset, it will raise a scroll event. + // But if we have a manual scroll request pending, we need to mute it and wait for the next layout updated. + return; } + _pendingScrollTo = default; + // We don't have any information from the DOM 'scroll' event about the intermediate vs. final state. // We could try to rely on the IsPointerPressed state to detect when the user is scrolling and use it. // This would however not include scrolling due to the inertia which should also be flagged as intermediate. @@ -210,12 +260,22 @@ private void OnScroll(object sender, EventArgs args) var isIntermediate = false; (TemplatedParent as ScrollViewer)?.OnScrollInternal( - horizontalOffset, - verticalOffset, + GetNativeHorizontalOffset(), + GetNativeVerticalOffset(), isIntermediate ); } + private double GetNativeHorizontalOffset() + => double.TryParse(GetProperty("scrollLeft"), NumberStyles.Number, CultureInfo.InvariantCulture, out var horizontalOffset) + ? horizontalOffset + : 0; + + private double GetNativeVerticalOffset() + => double.TryParse(GetProperty("scrollTop"), NumberStyles.Number, CultureInfo.InvariantCulture, out var verticalOffset) + ? verticalOffset + : 0; + void IScrollContentPresenter.OnMinZoomFactorChanged(float newValue) { } void IScrollContentPresenter.OnMaxZoomFactorChanged(float newValue) { } diff --git a/src/Uno.UI/UI/Xaml/FrameworkElement.EffectiveViewport.cs b/src/Uno.UI/UI/Xaml/FrameworkElement.EffectiveViewport.cs index 1fd5edb517bf..f15ecae7c2fd 100644 --- a/src/Uno.UI/UI/Xaml/FrameworkElement.EffectiveViewport.cs +++ b/src/Uno.UI/UI/Xaml/FrameworkElement.EffectiveViewport.cs @@ -96,7 +96,7 @@ private void ReconfigureViewportPropagation(IFrameworkElement_EffectiveViewport } else { - TRACE_EFFECTIVE_VIEWPORT("New child requested viewport propagation which has already been enabled. Force updating all children."); + TRACE_EFFECTIVE_VIEWPORT("New child requested viewport propagation which has already been enabled, forwarding current viewport to it."); // We are already subscribed, the parent won't send any update (and our _parentViewport is expected to be up-to-date). // But if this "reconfigure" was made for a new child (child != null), we have to initialize its own _parentViewport.