Batch flip-view range updates for programmatic calendar navigation (#805)

* Batch calendar range updates during programmatic navigation

* Refine programmatic calendar navigation batching state
This commit is contained in:
Burak Kaan Köse
2026-02-13 14:37:24 +01:00
committed by GitHub
parent dbd5812c45
commit 92df726f34
2 changed files with 83 additions and 21 deletions
@@ -142,7 +142,14 @@ public partial class WinoCalendarControl : Control
} }
private void ManageHighlightedDateRange() private void ManageHighlightedDateRange()
=> SelectedFlipViewDayRange = InternalFlipView.SelectedItem as DayRangeRenderModel; {
if (InternalFlipView?.IsProgrammaticNavigationInProgress == true)
{
return;
}
SelectedFlipViewDayRange = InternalFlipView?.SelectedItem as DayRangeRenderModel;
}
private void DeregisterCanvas(WinoDayTimelineCanvas canvas) private void DeregisterCanvas(WinoDayTimelineCanvas canvas)
{ {
@@ -190,14 +197,29 @@ public partial class WinoCalendarControl : Control
{ {
base.OnApplyTemplate(); base.OnApplyTemplate();
if (InternalFlipView != null)
{
InternalFlipView.ProgrammaticNavigationCompleted -= InternalFlipViewProgrammaticNavigationCompleted;
}
InternalFlipView = GetTemplateChild(PART_WinoFlipView) as WinoCalendarFlipView; InternalFlipView = GetTemplateChild(PART_WinoFlipView) as WinoCalendarFlipView;
IdleGrid = GetTemplateChild(PART_IdleGrid) as Grid; IdleGrid = GetTemplateChild(PART_IdleGrid) as Grid;
if (InternalFlipView != null)
{
InternalFlipView.ProgrammaticNavigationCompleted += InternalFlipViewProgrammaticNavigationCompleted;
}
UpdateIdleState(); UpdateIdleState();
ManageCalendarOrientation(); ManageCalendarOrientation();
ManageDisplayType(); ManageDisplayType();
} }
private void InternalFlipViewProgrammaticNavigationCompleted(object? sender, ProgrammaticNavigationCompletedEventArgs e)
{
SelectedFlipViewDayRange = e.DayRange;
}
private void UpdateIdleState() private void UpdateIdleState()
{ {
InternalFlipView.Opacity = IsFlipIdle ? 0 : 1; InternalFlipView.Opacity = IsFlipIdle ? 0 : 1;
@@ -43,6 +43,12 @@ public partial class WinoCalendarFlipView : CustomCalendarFlipView
set { SetValue(IsIdleProperty, value); } set { SetValue(IsIdleProperty, value); }
} }
internal bool IsProgrammaticNavigationInProgress { get; private set; }
internal int? PendingTargetIndex { get; private set; }
internal event EventHandler<ProgrammaticNavigationCompletedEventArgs>? ProgrammaticNavigationCompleted;
public WinoCalendarFlipView() public WinoCalendarFlipView()
{ {
RegisterPropertyChangedCallback(ItemsSourceProperty, new DependencyPropertyChangedCallback(OnItemsSourceChanged)); RegisterPropertyChangedCallback(ItemsSourceProperty, new DependencyPropertyChangedCallback(OnItemsSourceChanged));
@@ -123,37 +129,61 @@ public partial class WinoCalendarFlipView : CustomCalendarFlipView
await DispatcherQueue.EnqueueAsync(() => await DispatcherQueue.EnqueueAsync(() =>
{ {
// Find the day range that contains the date. // Find the day range that contains the date.
var dayRange = GetItemsSource()?.FirstOrDefault(a => a.CalendarDays.Any(b => b.RepresentingDate.Date == dateTime.Date)); var dayRanges = GetItemsSource();
var dayRange = dayRanges?.FirstOrDefault(a => a.CalendarDays.Any(b => b.RepresentingDate.Date == dateTime.Date));
if (dayRange != null) if (dayRange != null && dayRanges != null)
{ {
var navigationItemIndex = GetItemsSource().IndexOf(dayRange); var navigationItemIndex = dayRanges.IndexOf(dayRange);
var hasNavigationWork = navigationItemIndex != SelectedIndex;
if (Math.Abs(navigationItemIndex - SelectedIndex) > 4) IsProgrammaticNavigationInProgress = hasNavigationWork;
PendingTargetIndex = navigationItemIndex;
if (!hasNavigationWork)
{ {
// Difference between dates are high. PendingTargetIndex = null;
// No need to animate this much, just go without animating. return;
SelectedIndex = navigationItemIndex;
} }
else
{
// Until we reach the day in the flip, simulate next-prev button clicks.
// This will make sure the FlipView animations are triggered.
// Setting SelectedIndex directly doesn't trigger the animations.
while (SelectedIndex != navigationItemIndex) try
{
if (Math.Abs(navigationItemIndex - SelectedIndex) > 4)
{ {
if (SelectedIndex > navigationItemIndex) // Difference between dates are high.
// No need to animate this much, just go without animating.
SelectedIndex = navigationItemIndex;
}
else
{
// Until we reach the day in the flip, simulate next-prev button clicks.
// This will make sure the FlipView animations are triggered.
// Setting SelectedIndex directly doesn't trigger the animations.
while (SelectedIndex != navigationItemIndex)
{ {
GoPreviousFlip(); if (SelectedIndex > navigationItemIndex)
} {
else GoPreviousFlip();
{ }
GoNextFlip(); else
{
GoNextFlip();
}
} }
} }
} }
finally
{
if (SelectedIndex == PendingTargetIndex)
{
ProgrammaticNavigationCompleted?.Invoke(this, new ProgrammaticNavigationCompletedEventArgs(SelectedItem as DayRangeRenderModel ?? dayRange));
}
IsProgrammaticNavigationInProgress = false;
PendingTargetIndex = null;
}
} }
}); });
} }
@@ -161,3 +191,13 @@ public partial class WinoCalendarFlipView : CustomCalendarFlipView
private ObservableRangeCollection<DayRangeRenderModel> GetItemsSource() private ObservableRangeCollection<DayRangeRenderModel> GetItemsSource()
=> ItemsSource as ObservableRangeCollection<DayRangeRenderModel>; => ItemsSource as ObservableRangeCollection<DayRangeRenderModel>;
} }
internal sealed class ProgrammaticNavigationCompletedEventArgs : EventArgs
{
public ProgrammaticNavigationCompletedEventArgs(DayRangeRenderModel dayRange)
{
DayRange = dayRange;
}
public DayRangeRenderModel DayRange { get; }
}