using System; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Wino.Calendar.Args; using Wino.Calendar.ViewModels.Data; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.Calendar; using Wino.Helpers; namespace Wino.Calendar.Controls; public partial class WinoCalendarControl : Control { private const string PART_WinoFlipView = nameof(PART_WinoFlipView); private const string PART_IdleGrid = nameof(PART_IdleGrid); public event EventHandler TimelineCellSelected; public event EventHandler TimelineCellUnselected; public event EventHandler ScrollPositionChanging; #region Dependency Properties public static readonly DependencyProperty DayRangesProperty = DependencyProperty.Register(nameof(DayRanges), typeof(ObservableCollection), typeof(WinoCalendarControl), new PropertyMetadata(null)); public static readonly DependencyProperty SelectedFlipViewIndexProperty = DependencyProperty.Register(nameof(SelectedFlipViewIndex), typeof(int), typeof(WinoCalendarControl), new PropertyMetadata(-1)); public static readonly DependencyProperty SelectedFlipViewDayRangeProperty = DependencyProperty.Register(nameof(SelectedFlipViewDayRange), typeof(DayRangeRenderModel), typeof(WinoCalendarControl), new PropertyMetadata(null)); public static readonly DependencyProperty ActiveCanvasProperty = DependencyProperty.Register(nameof(ActiveCanvas), typeof(WinoDayTimelineCanvas), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveCanvasChanged))); public static readonly DependencyProperty IsFlipIdleProperty = DependencyProperty.Register(nameof(IsFlipIdle), typeof(bool), typeof(WinoCalendarControl), new PropertyMetadata(true, new PropertyChangedCallback(OnIdleStateChanged))); public static readonly DependencyProperty ActiveScrollViewerProperty = DependencyProperty.Register(nameof(ActiveScrollViewer), typeof(ScrollViewer), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveVerticalScrollViewerChanged))); public static readonly DependencyProperty VerticalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(VerticalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated))); public static readonly DependencyProperty HorizontalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(HorizontalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated))); public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(nameof(Orientation), typeof(CalendarOrientation), typeof(WinoCalendarControl), new PropertyMetadata(CalendarOrientation.Horizontal, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated))); public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register(nameof(DisplayType), typeof(CalendarDisplayType), typeof(WinoCalendarControl), new PropertyMetadata(CalendarDisplayType.Day)); /// /// Gets or sets the day-week-month-year display type. /// Orientation is not determined by this property, but Orientation property. /// This property is used to determine the template to use for the calendar. /// public CalendarDisplayType DisplayType { get { return (CalendarDisplayType)GetValue(DisplayTypeProperty); } set { SetValue(DisplayTypeProperty, value); } } public CalendarOrientation Orientation { get { return (CalendarOrientation)GetValue(OrientationProperty); } set { SetValue(OrientationProperty, value); } } public ItemsPanelTemplate VerticalItemsPanelTemplate { get { return (ItemsPanelTemplate)GetValue(VerticalItemsPanelTemplateProperty); } set { SetValue(VerticalItemsPanelTemplateProperty, value); } } public ItemsPanelTemplate HorizontalItemsPanelTemplate { get { return (ItemsPanelTemplate)GetValue(HorizontalItemsPanelTemplateProperty); } set { SetValue(HorizontalItemsPanelTemplateProperty, value); } } public DayRangeRenderModel SelectedFlipViewDayRange { get { return (DayRangeRenderModel)GetValue(SelectedFlipViewDayRangeProperty); } set { SetValue(SelectedFlipViewDayRangeProperty, value); } } public ScrollViewer ActiveScrollViewer { get { return (ScrollViewer)GetValue(ActiveScrollViewerProperty); } set { SetValue(ActiveScrollViewerProperty, value); } } public WinoDayTimelineCanvas ActiveCanvas { get { return (WinoDayTimelineCanvas)GetValue(ActiveCanvasProperty); } set { SetValue(ActiveCanvasProperty, value); } } public bool IsFlipIdle { get { return (bool)GetValue(IsFlipIdleProperty); } set { SetValue(IsFlipIdleProperty, value); } } /// /// Gets or sets the collection of day ranges to render. /// Each day range usually represents a week, but it may support other ranges. /// public ObservableCollection DayRanges { get { return (ObservableCollection)GetValue(DayRangesProperty); } set { SetValue(DayRangesProperty, value); } } public int SelectedFlipViewIndex { get { return (int)GetValue(SelectedFlipViewIndexProperty); } set { SetValue(SelectedFlipViewIndexProperty, value); } } #endregion private WinoCalendarFlipView InternalFlipView; private Grid IdleGrid; public WinoCalendarControl() { DefaultStyleKey = typeof(WinoCalendarControl); SizeChanged += CalendarSizeChanged; } private static void OnCalendarOrientationPropertiesUpdated(DependencyObject calendar, DependencyPropertyChangedEventArgs e) { if (calendar is WinoCalendarControl control) { control.ManageCalendarOrientation(); } } private static void OnIdleStateChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e) { if (calendar is WinoCalendarControl calendarControl) { calendarControl.UpdateIdleState(); } } private static void OnActiveVerticalScrollViewerChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e) { if (calendar is WinoCalendarControl calendarControl) { if (e.OldValue is ScrollViewer oldScrollViewer) { calendarControl.DeregisterScrollChanges(oldScrollViewer); } if (e.NewValue is ScrollViewer newScrollViewer) { calendarControl.RegisterScrollChanges(newScrollViewer); } calendarControl.ManageHighlightedDateRange(); } } private static void OnActiveCanvasChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e) { if (calendar is WinoCalendarControl calendarControl) { if (e.OldValue is WinoDayTimelineCanvas oldCanvas) { // Dismiss any selection on the old canvas. calendarControl.DeregisterCanvas(oldCanvas); } if (e.NewValue is WinoDayTimelineCanvas newCanvas) { calendarControl.RegisterCanvas(newCanvas); } calendarControl.ManageHighlightedDateRange(); } } private void ManageCalendarOrientation() { if (InternalFlipView == null || HorizontalItemsPanelTemplate == null || VerticalItemsPanelTemplate == null) return; InternalFlipView.ItemsPanel = Orientation == CalendarOrientation.Horizontal ? HorizontalItemsPanelTemplate : VerticalItemsPanelTemplate; } private void ManageHighlightedDateRange() => SelectedFlipViewDayRange = InternalFlipView.SelectedItem as DayRangeRenderModel; private void DeregisterCanvas(WinoDayTimelineCanvas canvas) { if (canvas == null) return; canvas.SelectedDateTime = null; canvas.TimelineCellSelected -= ActiveTimelineCellSelected; canvas.TimelineCellUnselected -= ActiveTimelineCellUnselected; } private void RegisterCanvas(WinoDayTimelineCanvas canvas) { if (canvas == null) return; canvas.SelectedDateTime = null; canvas.TimelineCellSelected += ActiveTimelineCellSelected; canvas.TimelineCellUnselected += ActiveTimelineCellUnselected; } private void RegisterScrollChanges(ScrollViewer scrollViewer) { if (scrollViewer == null) return; scrollViewer.ViewChanging += ScrollViewChanging; } private void DeregisterScrollChanges(ScrollViewer scrollViewer) { if (scrollViewer == null) return; scrollViewer.ViewChanging -= ScrollViewChanging; } private void ScrollViewChanging(object sender, ScrollViewerViewChangingEventArgs e) => ScrollPositionChanging?.Invoke(this, EventArgs.Empty); private void CalendarSizeChanged(object sender, SizeChangedEventArgs e) { if (ActiveCanvas == null) return; ActiveCanvas.SelectedDateTime = null; } protected override void OnApplyTemplate() { base.OnApplyTemplate(); InternalFlipView = GetTemplateChild(PART_WinoFlipView) as WinoCalendarFlipView; IdleGrid = GetTemplateChild(PART_IdleGrid) as Grid; UpdateIdleState(); ManageCalendarOrientation(); } private void UpdateIdleState() { InternalFlipView.Opacity = IsFlipIdle ? 0 : 1; IdleGrid.Visibility = IsFlipIdle ? Visibility.Visible : Visibility.Collapsed; } private void ActiveTimelineCellUnselected(object sender, TimelineCellUnselectedArgs e) => TimelineCellUnselected?.Invoke(this, e); private void ActiveTimelineCellSelected(object sender, TimelineCellSelectedArgs e) => TimelineCellSelected?.Invoke(this, e); public void NavigateToDay(DateTime dateTime) => InternalFlipView.NavigateToDay(dateTime); public async void NavigateToHour(TimeSpan timeSpan) { if (ActiveScrollViewer == null) return; // Total height of the FlipViewItem is the same as vertical ScrollViewer to position day headers. await Task.Yield(); await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () => { double hourHeght = 60; double totalHeight = ActiveScrollViewer.ScrollableHeight; double scrollPosition = timeSpan.TotalHours * hourHeght; ActiveScrollViewer.ChangeView(null, scrollPosition, null, disableAnimation: false); }); } public void ResetTimelineSelection() { if (ActiveCanvas == null) return; ActiveCanvas.SelectedDateTime = null; } public void GoNextRange() { if (InternalFlipView == null) return; InternalFlipView.GoNextFlip(); } public void GoPreviousRange() { if (InternalFlipView == null) return; InternalFlipView.GoPreviousFlip(); } public void UnselectActiveTimelineCell() { if (ActiveCanvas == null) return; ActiveCanvas.SelectedDateTime = null; } public CalendarItemControl GetCalendarItemControl(CalendarItemViewModel calendarItemViewModel) { return this.FindDescendants().FirstOrDefault(a => a.CalendarItem == calendarItemViewModel); } }