From c9d32d0eaf621c70ac26e08de1e0c6707acfd9f9 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 3 Dec 2020 17:06:47 -0500 Subject: [PATCH 01/81] chore(calendar): Import calendar C++ code from WinUI --- .../CalendarDatePicker.Properties.cs | 170 ++ ...alendarDatePickerAutomationPeer_Partial.cs | 105 + .../CalendarLayoutStrategy_Partial.cs | 316 ++ ...endarScrollViewerAutomationPeer_Partial.cs | 87 + .../CalendarViewAutomationPeer_Partial.cs | 503 +++ ...endarViewBaseItemAutomationPeer_Partial.cs | 131 + .../CalendarViewBaseItem_Partial.cs | 398 +++ ...lendarViewDayItemAutomationPeer_Partial.cs | 321 ++ ...darViewDayItemChangingEventArgs_Partial.cs | 45 + .../CalendarViewDayItem_Partial.cs | 169 + .../CalendarViewGeneratorDecadeViewHost.cs | 315 ++ .../CalendarView/CalendarViewGeneratorHost.cs | 707 +++++ ...darViewGeneratorHost_DataVirtualization.cs | 92 + .../CalendarViewGeneratorMonthViewHost.cs | 323 ++ ...ewGeneratorMonthViewHost_ContainerPhase.cs | 753 +++++ .../CalendarViewGeneratorYearViewHost.cs | 269 ++ .../CalendarViewItemAutomationPeer_Partial.cs | 157 + .../CalendarView/CalendarView_Partial.cs | 2720 +++++++++++++++++ .../CalendarView_Partial_Interaction.cs | 460 +++ .../CalendarView_Partial_Selection.cs | 466 +++ .../CalendarView_Partial_TimeZone.cs | 62 + .../Primitives/CalendarPanelType.cs | 13 + .../Primitives/CalendarPanel_Partial.cs | 526 ++++ .../CalendarViewTemplateSettings.cs | 56 + .../calendardatepicker_partial.cs | 1047 +++++++ .../CalendarView/calendarviewitem_partial.cs | 108 + 26 files changed, 10319 insertions(+) create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarDatePicker.Properties.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarDatePickerAutomationPeer_Partial.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarLayoutStrategy_Partial.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarScrollViewerAutomationPeer_Partial.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewAutomationPeer_Partial.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewBaseItemAutomationPeer_Partial.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewBaseItem_Partial.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewDayItemAutomationPeer_Partial.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewDayItemChangingEventArgs_Partial.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewDayItem_Partial.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorDecadeViewHost.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorHost.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorHost_DataVirtualization.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorMonthViewHost.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorMonthViewHost_ContainerPhase.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorYearViewHost.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewItemAutomationPeer_Partial.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarView_Partial.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarView_Partial_Interaction.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarView_Partial_Selection.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarView_Partial_TimeZone.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/Primitives/CalendarPanelType.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/Primitives/CalendarPanel_Partial.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/Primitives/CalendarViewTemplateSettings.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/calendardatepicker_partial.cs create mode 100644 src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/calendarviewitem_partial.cs diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarDatePicker.Properties.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarDatePicker.Properties.cs new file mode 100644 index 000000000000..d37a1f591879 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarDatePicker.Properties.cs @@ -0,0 +1,170 @@ +using System; +using System.Linq; + +namespace Windows.UI.Xaml.Controls +{ + partial class CalendarDatePicker + { + public static readonly DependencyProperty CalendarIdentifierProperty = DependencyProperty.Register( + "CalendarIdentifier", typeof(string), typeof(CalendarDatePicker), new PropertyMetadata(default(string))); + + public string CalendarIdentifier + { + get { return (string)GetValue(CalendarIdentifierProperty); } + set { SetValue(CalendarIdentifierProperty, value); } + } + + public static readonly DependencyProperty CalendarViewStyleProperty = DependencyProperty.Register( + "CalendarViewStyle", typeof(Style), typeof(CalendarDatePicker), new PropertyMetadata(default(Style))); + + public Style CalendarViewStyle + { + get { return (Style)GetValue(CalendarViewStyleProperty); } + set { SetValue(CalendarViewStyleProperty, value); } + } + + public static readonly DependencyProperty DateProperty = DependencyProperty.Register( + "Date", typeof(DateTimeOffset?), typeof(CalendarDatePicker), new PropertyMetadata(default(DateTimeOffset))); + + public DateTimeOffset? Date + { + get { return (DateTimeOffset?)GetValue(DateProperty); } + set { SetValue(DateProperty, value); } + } + + public static readonly DependencyProperty DateFormatProperty = DependencyProperty.Register( + "DateFormat", typeof(string), typeof(CalendarDatePicker), new PropertyMetadata(default(string))); + + public string DateFormat + { + get { return (string)GetValue(DateFormatProperty); } + set { SetValue(DateFormatProperty, value); } + } + + public static readonly DependencyProperty DayOfWeekFormatProperty = DependencyProperty.Register( + "DayOfWeekFormat", typeof(string), typeof(CalendarDatePicker), new PropertyMetadata(default(string))); + + public string DayOfWeekFormat + { + get { return (string)GetValue(DayOfWeekFormatProperty); } + set { SetValue(DayOfWeekFormatProperty, value); } + } + + public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( + "Description", typeof(string), typeof(CalendarDatePicker), new PropertyMetadata(default(string))); + + public string Description + { + get { return (string)GetValue(DescriptionProperty); } + set { SetValue(DescriptionProperty, value); } + } + + public static readonly DependencyProperty DisplayModeProperty = DependencyProperty.Register( + "DisplayMode", typeof(CalendarViewDisplayMode), typeof(CalendarDatePicker), new PropertyMetadata(default(CalendarViewDisplayMode))); + + public CalendarViewDisplayMode DisplayMode + { + get { return (CalendarViewDisplayMode)GetValue(DisplayModeProperty); } + set { SetValue(DisplayModeProperty, value); } + } + + public static readonly DependencyProperty FirstDayOfWeekProperty = DependencyProperty.Register( + "FirstDayOfWeek", typeof(DayOfWeek), typeof(CalendarDatePicker), new PropertyMetadata(default(DayOfWeek))); + + public DayOfWeek FirstDayOfWeek + { + get { return (DayOfWeek)GetValue(FirstDayOfWeekProperty); } + set { SetValue(FirstDayOfWeekProperty, value); } + } + + public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register( + "Header", typeof(object), typeof(CalendarDatePicker), new PropertyMetadata(default(object))); + + public object Header + { + get { return (object)GetValue(HeaderProperty); } + set { SetValue(HeaderProperty, value); } + } + + public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register( + "HeaderTemplate", typeof(DataTemplate), typeof(CalendarDatePicker), new PropertyMetadata(default(DataTemplate))); + + public DataTemplate HeaderTemplate + { + get { return (DataTemplate)GetValue(HeaderTemplateProperty); } + set { SetValue(HeaderTemplateProperty, value); } + } + + public static readonly DependencyProperty IsCalendarOpenProperty = DependencyProperty.Register( + "IsCalendarOpen", typeof(bool), typeof(CalendarDatePicker), new PropertyMetadata(default(bool))); + + public bool IsCalendarOpen + { + get { return (bool)GetValue(IsCalendarOpenProperty); } + set { SetValue(IsCalendarOpenProperty, value); } + } + + public static readonly DependencyProperty IsGroupLabelVisibleProperty = DependencyProperty.Register( + "IsGroupLabelVisible", typeof(bool), typeof(CalendarDatePicker), new PropertyMetadata(default(bool))); + + public bool IsGroupLabelVisible + { + get { return (bool)GetValue(IsGroupLabelVisibleProperty); } + set { SetValue(IsGroupLabelVisibleProperty, value); } + } + + public static readonly DependencyProperty IsOutOfScopeEnabledProperty = DependencyProperty.Register( + "IsOutOfScopeEnabled", typeof(bool), typeof(CalendarDatePicker), new PropertyMetadata(default(bool))); + + public bool IsOutOfScopeEnabled + { + get { return (bool)GetValue(IsOutOfScopeEnabledProperty); } + set { SetValue(IsOutOfScopeEnabledProperty, value); } + } + + public static readonly DependencyProperty IsTodayHighlightedProperty = DependencyProperty.Register( + "IsTodayHighlighted", typeof(bool), typeof(CalendarDatePicker), new PropertyMetadata(default(bool))); + + public bool IsTodayHighlighted + { + get { return (bool)GetValue(IsTodayHighlightedProperty); } + set { SetValue(IsTodayHighlightedProperty, value); } + } + + public static readonly DependencyProperty LightDismissOverlayModeProperty = DependencyProperty.Register( + "LightDismissOverlayMode", typeof(LightDismissOverlayMode), typeof(CalendarDatePicker), new PropertyMetadata(default(LightDismissOverlayMode))); + + public LightDismissOverlayMode LightDismissOverlayMode + { + get { return (LightDismissOverlayMode)GetValue(LightDismissOverlayModeProperty); } + set { SetValue(LightDismissOverlayModeProperty, value); } + } + + public static readonly DependencyProperty MaxDateProperty = DependencyProperty.Register( + "MaxDate", typeof(DateTimeOffset), typeof(CalendarDatePicker), new PropertyMetadata(default(DateTimeOffset))); + + public DateTimeOffset MaxDate + { + get { return (DateTimeOffset)GetValue(MaxDateProperty); } + set { SetValue(MaxDateProperty, value); } + } + + public static readonly DependencyProperty MinDateProperty = DependencyProperty.Register( + "MinDate", typeof(DateTimeOffset), typeof(CalendarDatePicker), new PropertyMetadata(default(DateTimeOffset))); + + public DateTimeOffset MinDate + { + get { return (DateTimeOffset)GetValue(MinDateProperty); } + set { SetValue(MinDateProperty, value); } + } + + public static readonly DependencyProperty PlaceholderTextProperty = DependencyProperty.Register( + "PlaceholderText", typeof(string), typeof(CalendarDatePicker), new PropertyMetadata(default(string))); + + public string PlaceholderText + { + get { return (string)GetValue(PlaceholderTextProperty); } + set { SetValue(PlaceholderTextProperty, value); } + } + } +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarDatePickerAutomationPeer_Partial.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarDatePickerAutomationPeer_Partial.cs new file mode 100644 index 000000000000..f2d305f9a34f --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarDatePickerAutomationPeer_Partial.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace Windows.UI.Xaml.Controls +{ + partial class CalendarDatePicker + { + //CalendarDatePickerAutomationPeerFactory.CreateInstanceWithOwnerImpl + IMPLEMENT_CONTROL_AUTOMATIONPEERFACTORY_CREATEINSTANCE(CalendarDatePicker) + + // Initializes a new instance of the CalendarDatePickerAutomationPeer class. + CalendarDatePickerAutomationPeer.CalendarDatePickerAutomationPeer() + { + } + + // Deconstructor + CalendarDatePickerAutomationPeer.~CalendarDatePickerAutomationPeer() + { + } + + IFACEMETHODIMP CalendarDatePickerAutomationPeer.GetPatternCore(xaml_automation_peers.PatternInterface patternInterface, _Outptr_ IInspectable** ppReturnValue) + { + HRESULT hr = S_OK; + IFCPTR(ppReturnValue); + *ppReturnValue = NULL; + + if (patternInterface == xaml_automation_peers.PatternInterface_Invoke + || patternInterface == xaml_automation_peers.PatternInterface_Value) + { + *ppReturnValue = ctl.as_iinspectable(this); + ctl.addref_interface(this); + } + else + { + IFC(CalendarDatePickerAutomationPeerGenerated.GetPatternCore(patternInterface, ppReturnValue)); + } + + Cleanup: + RRETURN(hr); + } + + IFACEMETHODIMP CalendarDatePickerAutomationPeer.GetClassNameCore(out HSTRING* returnValue) + { + RRETURN(wrl_wrappers.Hstring(STR_LEN_PAIR("CalendarDatePicker")).CopyTo(returnValue)); + } + + IFACEMETHODIMP CalendarDatePickerAutomationPeer.GetAutomationControlTypeCore(out xaml_automation_peers.AutomationControlType* pReturnValue) + { + *pReturnValue = xaml_automation_peers.AutomationControlType_Button; + RRETURN(S_OK); + } + + IFACEMETHODIMP CalendarDatePickerAutomationPeer.GetLocalizedControlTypeCore(out HSTRING* returnValue) + { + IFC_RETURN(DXamlCore.GetCurrentNoCreate().GetLocalizedResourceString(UIA_AP_CALENDARDATEPICKER, returnValue)); + + return S_OK; + } + + _Check_return_ HRESULT CalendarDatePickerAutomationPeer.InvokeImpl() + { + HRESULT hr = S_OK; + xaml.IUIElement* pOwner = NULL; + BOOLEAN bIsEnabled; + + IFC(IsEnabled(&bIsEnabled)); + if (!bIsEnabled) + { + IFC(UIA_E_ELEMENTNOTENABLED); + } + + IFC(get_Owner(&pOwner)); + IFCPTR(pOwner); + IFC(((CalendarDatePicker*)(pOwner)).put_IsCalendarOpen(TRUE)); + + Cleanup: + ReleaseInterface(pOwner); + RRETURN(hr); + } + + _Check_return_ HRESULT CalendarDatePickerAutomationPeer.get_IsReadOnlyImpl(out BOOLEAN* value) + { + *value = TRUE; + return S_OK; + } + + _Check_return_ HRESULT CalendarDatePickerAutomationPeer.get_ValueImpl(out HSTRING* value) + { + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + var ownerItem = spOwner.AsOrNull(); + IFCPTR_RETURN(ownerItem); + + IFC_RETURN(ownerItem.GetCurrentFormattedDate(value)); + + return S_OK; + } + + _Check_return_ HRESULT CalendarDatePickerAutomationPeer.SetValueImpl(HSTRING value) + { + return E_NOTIMPL; + } + } +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarLayoutStrategy_Partial.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarLayoutStrategy_Partial.cs new file mode 100644 index 000000000000..f0f3406bacb8 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarLayoutStrategy_Partial.cs @@ -0,0 +1,316 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using Windows.Foundation; + +namespace Windows.UI.Xaml.Controls +{ + public partial class CalendarLayoutStrategy : Control + { + private CalendarLayoutStrategyImpl _layoutStrategyImpl; + + private ILayoutDataInfoProvider _spDataInfoProvider; + + private void SetLayoutDataInfoProviderImpl(ILayoutDataInfoProvider pProvider) /*override*/ + { + _spDataInfoProvider = pProvider; + _layoutStrategyImpl.SetLayoutDataInfoProviderNoRef(pProvider); + } + + #region Layout related methods + + private void BeginMeasureImpl() /*override*/ + { + _layoutStrategyImpl.BeginMeasure(); + } + + private void EndMeasureImpl() /*override*/ + { + _layoutStrategyImpl.EndMeasure(); + } + + // returns the size we should use to measure a container or header with + // itemIndex - indicates an index of valid item or -1 for general, non-special items + private void GetElementMeasureSizeImpl(ElementType elementType, int elementIndex, Rect windowConstraint, out Size pReturnValue) /*override*/ + { + pReturnValue = null; + pReturnValue = _layoutStrategyImpl.GetElementMeasureSize(elementType, elementIndex, windowConstraint); + } + + private void GetElementBoundsImpl( + ElementType elementType, + int elementIndex, + Size containerDesiredSize, + LayoutReference referenceInformation, + Rect windowConstraint, + out Rect pReturnValue) /*override*/ + { + pReturnValue = null; + pReturnValue = _layoutStrategyImpl.GetElementBounds( + elementType, + elementIndex, + containerDesiredSize, + referenceInformation, + windowConstraint); + } + + private void GetElementArrangeBoundsImpl( + ElementType elementType, + int elementIndex, + Rect containerBounds, + Rect windowConstraint, + Size finalSize, + out Rect pReturnValue) /*override*/ + { + pReturnValue = null; + pReturnValue = _layoutStrategyImpl.GetElementArrangeBounds( + elementType, + elementIndex, + containerBounds, + windowConstraint, + finalSize); + + return; + } + + private void ShouldContinueFillingUpSpaceImpl( + ElementType elementType, + int elementIndex, + LayoutReference referenceInformation, + Rect windowToFill, + out bool pReturnValue) /*override*/ + { + pReturnValue = null; + pReturnValue = !!_layoutStrategyImpl.ShouldContinueFillingUpSpace( + elementType, + elementIndex, + referenceInformation, + windowToFill); + + return; + } + + private void GetPositionOfFirstElementImpl(out Point returnValue) + { + returnValue = null; + returnValue = _layoutStrategyImpl.GetPositionOfFirstElement(); + + return; + } + + #endregion + + #region Estimation and virtualization related methods. + + private void GetVirtualizationDirectionImpl( + out Orientation pReturnValue) + { + pReturnValue = _layoutStrategyImpl.GetVirtualizationDirection(); + } + + private void EstimateElementIndexImpl( + ElementType elementType, + EstimationReference headerReference, + EstimationReference containerReference, + Rect window, + out Rect pTargetRect, + out int pReturnValue) /*override*/ + { + pReturnValue = null; + _layoutStrategyImpl.EstimateElementIndex( + elementType, + headerReference, + containerReference, + window, + pTargetRect, + pReturnValue)); + + return; + } + + // Estimate the location of an anchor group, using items-per-group to estimate an average group extent. + private void EstimateElementBoundsImpl( + ElementType elementType, + int elementIndex, + EstimationReference headerReference, + EstimationReference containerReference, + Rect window, + out Rect pReturnValue) /*override*/ + { + pReturnValue = null; + _layoutStrategyImpl.EstimateElementBounds( + elementType, + elementIndex, + headerReference, + containerReference, + window, + pReturnValue); + } + + private void EstimatePanelExtentImpl( + EstimationReference lastHeaderReference, + EstimationReference lastContainerReference, + Rect windowConstraint, + out Size pExtent) /*override*/ + { + pExtent = null; + _layoutStrategyImpl.EstimatePanelExtent( + lastHeaderReference, + lastContainerReference, + windowConstraint, + pExtent); + } + + #endregion + + #region IItemLookupPanel related + + // Estimates the index or the insertion index closest to the given point. + private void EstimateIndexFromPointImpl( + bool requestingInsertionIndex, + Point point, + EstimationReference reference, + Rect windowConstraint, + out IndexSearchHint pSearchHint, + out ElementType pElementType, + out int pElementIndex) /*override*/ + { + throw new NotImplementedException(); + } + + // Based on current element's index/type and action, return the next element index/type. + private void GetTargetIndexFromNavigationActionImpl( + ElementType elementType, + int elementIndex, + KeyNavigationAction action, + Rect windowConstraint, + int itemIndexHintForHeaderNavigation, + out ElementType targetElementType, + out int targetElementIndex) /*override*/ + { + targetElementType = ElementType.ItemContainer; + targetElementIndex = 0; + _layoutStrategyImpl.GetTargetIndexFromNavigationAction( + elementType, + elementIndex, + action, + windowConstraint, + targetElementType, + targetElementIndex); + } + + // Determines whether or not the given item index + // is a layout boundary. + private void IsIndexLayoutBoundaryImpl( + ElementType elementType, + int elementIndex, + Rect windowConstraint, + out bool pIsLeftBoundary, + out bool pIsTopBoundary, + out bool pIsRightBoundary, + out bool pIsBottomBoundary) /*override*/ + { + throw new NotImplementedException(); + } + + #endregion + + #region Snap points related + + private void GetRegularSnapPointsImpl( + out float pNearOffset, + out float pFarOffset, + out float pSpacing, + out bool pHasRegularSnapPoints) /*override*/ + { + pHasRegularSnapPoints = false; + pHasRegularSnapPoints = !!_layoutStrategyImpl.GetRegularSnapPoints( + pNearOffset, + pFarOffset, + pSpacing); + } + + private void HasIrregularSnapPointsImpl( + ElementType elementType, + out bool returnValue) /*override*/ + { + returnValue = false; + returnValue = !!_layoutStrategyImpl.HasIrregularSnapPoints(elementType); + } + + private void HasSnapPointOnElementImpl( + ElementType elementType, + int elementIndex, + out bool returnValue) + { + returnValue = false; + bool result = false; + _layoutStrategyImpl.HasSnapPointOnElement(elementType, elementIndex, result)); + returnValue = result; + } + #endregion + + private void GetIsWrappingStrategyImpl(out bool returnValue) /*override*/ + { + returnValue = false; + returnValue = !!_layoutStrategyImpl.GetIsWrappingStrategy(); + } + + private void GetElementTransitionsBoundsImpl( + ElementType elementType, + int elementIndex, + Rect windowConstraint, + out Rect pReturnValue) /*override*/ + { + throw new NotImplementedException(); + } + + + #region Special elements methods + + private bool NeedsSpecialItem() + { + return _layoutStrategyImpl.NeedsSpecialItem(); + } + + private int GetSpecialItemIndex() + { + return _layoutStrategyImpl.GetSpecialItemIndex(); + } + + #endregion + + + private Size GetDesiredViewportSize() + { + return _layoutStrategyImpl.GetDesiredViewportSize(); + } + + private void SetSnapPointFilterFunction(Func func) + { + _layoutStrategyImpl.SetSnapPointFilterFunction(func); + } + + Components.Moco.CalendarLayoutStrategyImpl.IndexCorrectionTable GetIndexCorrectionTable() + { + return _layoutStrategyImpl.GetIndexCorrectionTable(); + } + + } + + internal enum ElementType + { + // Structural element that holds Inline elements. + Paragraph = 0, + + // Formatting element that may hold other Inline elements. + Inline = 1, + + // An explicit line break within a Paragraph. + LineBreak = 2, + + // An embedded object (UIElement). + Object = 3, + } +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarScrollViewerAutomationPeer_Partial.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarScrollViewerAutomationPeer_Partial.cs new file mode 100644 index 000000000000..10993788327b --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarScrollViewerAutomationPeer_Partial.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "precomp.h" +#include "CalendarScrollViewerAutomationPeer.g.h" +#include "CalendarView.g.h" +#include "CalendarViewGeneratorHost.h" +#include "CalendarPanel.g.h" +#include "CalendarViewBaseItem.g.h" +#include "CalendarViewBaseItemAutomationPeer.g.h" + +using namespace DirectUI; +using namespace DirectUISynonyms; + +IFACEMETHODIMP CalendarScrollViewerAutomationPeer.GetClassNameCore(out HSTRING* pReturnValue) +{ + IFCPTR_RETURN(pReturnValue); + IFC_RETURN(wrl_wrappers.Hstring(STR_LEN_PAIR("CalendarScrollViewer")).CopyTo(pReturnValue)); + + return S_OK; +} + +IFACEMETHODIMP CalendarScrollViewerAutomationPeer.GetChildrenCore(_Outptr_ wfc.IList** ppReturnValue) +{ + IFCPTR_RETURN(ppReturnValue); + + ctl.ComPtr spOwner; + ctl.ComPtr spOwnerAsFrameworkElement; + + IFC_RETURN(get_Owner(&spOwner)); + IFC_RETURN(spOwner.As(&spOwnerAsFrameworkElement)); + + ctl.ComPtr spTemplatedParent; + IFC_RETURN((spOwnerAsFrameworkElement.Cast()).get_TemplatedParent(&spTemplatedParent)); + + if (spTemplatedParent) + { + ctl.ComPtr + spCalendarView = spTemplatedParent.AsOrNull(); + + if (spCalendarView) + { + ctl.ComPtr spGeneratorHost; + IFC_RETURN((spCalendarView.Cast()).GetActiveGeneratorHost(&spGeneratorHost)); + var pCalendarPanel = spGeneratorHost.GetPanel(); + + if (pCalendarPanel) + { + int firstIndex = -1; + int lastIndex = -1; + + IFC_RETURN(pCalendarPanel.get_FirstVisibleIndex(&firstIndex)); + IFC_RETURN(pCalendarPanel.get_LastVisibleIndex(&lastIndex)); + + // This ScrollViewer automation peer ensures that for CalendarViews, accessible Items are restricted + // to visible Items. To go to next unit view, user scenario is to utilize next and previous button. + // Utilizing realized Items has a side effect due to bufferring, the first Item is a few months + // back then current Item leading to an awkward state. + if (firstIndex != -1 && lastIndex != -1) + { + ctl.ComPtr> spAPChildren; + IFC_RETURN(ctl.new TrackerCollection(&spAPChildren)); + + for (int index = firstIndex; index <= lastIndex; ++index) + { + ctl.ComPtr spChildAsIDO; + ctl.ComPtr spChildAsItem; + IFC_RETURN(pCalendarPanel.ContainerFromIndex(index, &spChildAsIDO)); + IFC_RETURN(spChildAsIDO.As(&spChildAsItem)); + + ctl.ComPtr spAutomationPeer; + IFC_RETURN((spChildAsItem.Cast()).GetOrCreateAutomationPeer(&spAutomationPeer)); + + if (spAutomationPeer) + { + IFC_RETURN(spAPChildren.Append(spAutomationPeer.Get())); + } + } + + IFC_RETURN(spAPChildren.CopyTo(ppReturnValue)); + } + } + } + } + + return S_OK; +} \ No newline at end of file diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewAutomationPeer_Partial.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewAutomationPeer_Partial.cs new file mode 100644 index 000000000000..b33b53506502 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewAutomationPeer_Partial.cs @@ -0,0 +1,503 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "precomp.h" +#include "CalendarViewAutomationPeer.g.h" +#include "Grid.g.h" +#include "CalendarView.g.h" +#include "CalendarPanel.g.h" +#include "CalendarViewBaseItem.g.h" +#include "CalendarViewGeneratorHost.h" +#include "CalendarViewSelectedDatesChangedEventArgs.g.h" +#include + +using namespace DirectUI; +using namespace DirectUISynonyms; + +IFACEMETHODIMP CalendarViewAutomationPeer.GetPatternCore( xaml_automation_peers.PatternInterface patternInterface, _Outptr_ IInspectable** ppReturnValue) +{ + IFCPTR_RETURN(ppReturnValue); + *ppReturnValue = null; + + if (patternInterface == xaml_automation_peers.PatternInterface_Table || + patternInterface == xaml_automation_peers.PatternInterface_Grid || + patternInterface == xaml_automation_peers.PatternInterface_Value || + patternInterface == xaml_automation_peers.PatternInterface_Selection) + { + *ppReturnValue = ctl.as_iinspectable(this); + ctl.addref_interface(this); + } + else + { + IFC_RETURN(CalendarViewAutomationPeerGenerated.GetPatternCore(patternInterface, ppReturnValue)); + } + + return S_OK; +} + +IFACEMETHODIMP CalendarViewAutomationPeer.GetClassNameCore(out HSTRING* pReturnValue) +{ + IFCPTR_RETURN(pReturnValue); + IFC_RETURN(wrl_wrappers.Hstring(STR_LEN_PAIR("CalendarView")).CopyTo(pReturnValue)); + + return S_OK; +} + +IFACEMETHODIMP CalendarViewAutomationPeer.GetAutomationControlTypeCore(out xaml_automation_peers.AutomationControlType* pReturnValue) +{ + IFCPTR_RETURN(pReturnValue); + *pReturnValue = xaml_automation_peers.AutomationControlType_Calendar; + + return S_OK; +} + +IFACEMETHODIMP CalendarViewAutomationPeer.GetChildrenCore(_Outptr_ wfc.IList** ppReturnValue) +{ + IFCPTR_RETURN(ppReturnValue); + + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + IFC_RETURN(CalendarViewAutomationPeerGenerated.GetChildrenCore(ppReturnValue)); + if (*ppReturnValue != null) + { + IFC_RETURN(RemoveAPs(*ppReturnValue)); + } + return S_OK; +} + +// This function removes views that are not active. +HRESULT CalendarViewAutomationPeer.RemoveAPs(__inout wfc.IList* pAPCollection) +{ + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + UINT count = 0; + IFC_RETURN(pAPCollection.get_Size(&count)); + for (int index = (int)count - 1; index >= 0; index--) + { + ctl.ComPtr spAutomationPeer; + IFC_RETURN(pAPCollection.GetAt(index, &spAutomationPeer)); + if (spAutomationPeer != null) + { + ctl.ComPtr spCurrent; + IFC_RETURN(spAutomationPeer.Cast().get_Owner(&spCurrent)); + + while (spCurrent != null && spCurrent.Get() != spOwner.Get()) + { + DOUBLE opacity = 1.0; + IFC_RETURN(spCurrent.get_Opacity(&opacity)); + if (opacity == 0.0) + { + IFC_RETURN(pAPCollection.RemoveAt(index)); + break; + } + ctl.ComPtr spParent; + IFC_RETURN(spCurrent.AsOrNull().get_Parent(&spParent)); + spCurrent = spParent.AsOrNull(); + } + } + } + + return S_OK; +} + +// Properties. +_Check_return_ HRESULT CalendarViewAutomationPeer.get_CanSelectMultipleImpl(out BOOLEAN* pValue) +{ + IFCPTR_RETURN(pValue); + *pValue = FALSE; + + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + + xaml_controls.CalendarViewSelectionMode selectionMode = xaml_controls.CalendarViewSelectionMode.CalendarViewSelectionMode_None; + IFC_RETURN(spOwner.Cast().get_SelectionMode(&selectionMode)); + + if (selectionMode == xaml_controls.CalendarViewSelectionMode.CalendarViewSelectionMode_Multiple) + { + *pValue = TRUE; + } + return S_OK; +} + +_Check_return_ HRESULT CalendarViewAutomationPeer.get_IsSelectionRequiredImpl(out BOOLEAN* pValue) +{ + IFCPTR_RETURN(pValue); + *pValue = FALSE; + return S_OK; +} + +_Check_return_ HRESULT CalendarViewAutomationPeer.get_IsReadOnlyImpl(out BOOLEAN* pValue) +{ + IFCPTR_RETURN(pValue); + *pValue = TRUE; + return S_OK; +} + +// This will be date string if single date is selected otherwise the name of header of view +_Check_return_ HRESULT CalendarViewAutomationPeer.get_ValueImpl(out HSTRING* pValue) +{ + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + UINT count = 0; + IFC_RETURN(spOwner.Cast().m_tpSelectedDates.get_Size(&count)); + if (count == 1) + { + wrl_wrappers.HString string; + ctl.ComPtr spFormatter; + IFC_RETURN(spOwner.Cast().CreateDateTimeFormatter(wrl_wrappers.Hstring(STR_LEN_PAIR("day month.full year")).Get(), &spFormatter)); + + wf.DateTime date; + IFC_RETURN(spOwner.Cast().m_tpSelectedDates.GetAt(0, &date)); + + IFC_RETURN(spFormatter.Format(date, string.GetAddressOf())); + IFC_RETURN(string.CopyTo(pValue)); + } + else + { + ctl.ComPtr spHost; + IFC_RETURN(spOwner.Cast().GetActiveGeneratorHost(&spHost)); + IFC_RETURN(spHost.GetHeaderTextOfCurrentScope().CopyTo(pValue)); + } + + return S_OK; +} + +_Check_return_ HRESULT CalendarViewAutomationPeer.get_RowOrColumnMajorImpl(out xaml_automation.RowOrColumnMajor* pValue) +{ + IFCPTR_RETURN(pValue); + *pValue = xaml_automation.RowOrColumnMajor.RowOrColumnMajor_RowMajor; + return S_OK; +} + +_Check_return_ HRESULT CalendarViewAutomationPeer.get_ColumnCountImpl(out INT* pValue) +{ + IFCPTR_RETURN(pValue); + *pValue = 0; + + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + ctl.ComPtr spHost; + IFC_RETURN(spOwner.Cast().GetActiveGeneratorHost(&spHost)); + CalendarPanel* pCalendarPanel = spHost.GetPanel(); + if (pCalendarPanel) + { + IFC_RETURN(pCalendarPanel.get_Cols(pValue)); + } + return S_OK; +} + +_Check_return_ HRESULT CalendarViewAutomationPeer.get_RowCountImpl(out INT* pValue) +{ + IFCPTR_RETURN(pValue); + *pValue = 0; + + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + ctl.ComPtr spHost; + IFC_RETURN(spOwner.Cast().GetActiveGeneratorHost(&spHost)); + CalendarPanel* pCalendarPanel = spHost.GetPanel(); + if (pCalendarPanel) + { + IFC_RETURN(pCalendarPanel.get_Rows(pValue)); + } + return S_OK; +} + +// This will be visible rows in the view, real number of rows will not provide any value +_Check_return_ HRESULT CalendarViewAutomationPeer.GetSelectionImpl(out UINT* pReturnValueCount, _Out_writes_to_ptr_(*pReturnValueCount) xaml_automation.Provider.IIRawElementProviderSimple*** ppReturnValue) +{ + IFCPTR_RETURN(pReturnValueCount); + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + *pReturnValueCount = 0; + + + xaml_controls.CalendarViewDisplayMode mode = xaml_controls.CalendarViewDisplayMode_Month; + IFC_RETURN(spOwner.Cast().get_DisplayMode(&mode)); + if (mode == xaml_controls.CalendarViewDisplayMode_Month) + { + UINT count = 0; + UINT realizedCount = 0; + + IFC_RETURN(spOwner.Cast().m_tpSelectedDates.get_Size(&count)); + if (count > 0) + { + ctl.ComPtr spHost; + IFC_RETURN(spOwner.Cast().GetActiveGeneratorHost(&spHost)); + + ctl.ComPtr> spAPChildren; + IFC_RETURN(ctl.ComObject>.CreateInstance(spAPChildren.ReleaseAndGetAddressOf())); + + var pPanel = spHost.GetPanel(); + if (pPanel) + { + for (UINT i = 0; i < count; i++) + { + wf.DateTime date; + int itemIndex = 0; + ctl.ComPtr spItemAsI; + ctl.ComPtr spItem; + ctl.ComPtr spItemPeerAsAP; + + IFC_RETURN(spOwner.Cast().m_tpSelectedDates.GetAt(i, &date)); + IFC_RETURN(spHost.CalculateOffsetFromMinDate(date, &itemIndex)); + + IFC_RETURN(pPanel.ContainerFromIndex(itemIndex, &spItemAsI)); + if (spItemAsI) + { + IFC_RETURN(spItemAsI.As(&spItem)); + IFC_RETURN(spItem.GetOrCreateAutomationPeer(&spItemPeerAsAP)); + IFC_RETURN(spAPChildren.Append(spItemPeerAsAP.Get())); + } + } + + IFC_RETURN(spAPChildren.get_Size(&realizedCount)); + if (realizedCount > 0) + { + UINT allocSize = sizeof(IIRawElementProviderSimple*) * realizedCount; + *ppReturnValue = (IIRawElementProviderSimple**)(CoTaskMemAlloc(allocSize)); + IFCOOMFAILFAST(*ppReturnValue); + ZeroMemory(*ppReturnValue, allocSize); + + for (UINT index = 0; index < realizedCount; index++) + { + ctl.ComPtr spItemPeerAsAP; + ctl.ComPtr spProvider; + IFC_RETURN(spAPChildren.GetAt(index, &spItemPeerAsAP)); + IFC_RETURN(ProviderFromPeer(spItemPeerAsAP.Get(), &spProvider)); + (*ppReturnValue)[index] = spProvider.Detach(); + } + } + *pReturnValueCount = realizedCount; + } + } + } + return S_OK; +} + +_Check_return_ HRESULT CalendarViewAutomationPeer.SetValueImpl( HSTRING value) +{ + return E_NOTIMPL; +} + +// This will returns the header text labels on the top only in the case of monthView +_Check_return_ HRESULT CalendarViewAutomationPeer.GetColumnHeadersImpl(out UINT* pReturnValueCount, _Out_writes_to_ptr_(*pReturnValueCount) xaml_automation.Provider.IIRawElementProviderSimple*** ppReturnValue) +{ + *pReturnValueCount = 0; + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + xaml_controls.CalendarViewDisplayMode mode = xaml_controls.CalendarViewDisplayMode_Month; + IFC_RETURN(spOwner.Cast().get_DisplayMode(&mode)); + if (mode == xaml_controls.CalendarViewDisplayMode_Month) + { + UINT nCount = 0; + ctl.ComPtr spWeekDayNames; + IFC_RETURN(spOwner.Cast().GetTemplatePart(STR_LEN_PAIR("WeekDayNames"), spWeekDayNames.ReleaseAndGetAddressOf())); + if (spWeekDayNames) + { + ctl.ComPtr> spChildren; + IFC_RETURN(spWeekDayNames.Cast().get_Children(&spChildren)); + IFC_RETURN(spChildren.get_Size(&nCount)); + + UINT allocSize = sizeof(IIRawElementProviderSimple*) * nCount; + *ppReturnValue = (IIRawElementProviderSimple**)(CoTaskMemAlloc(allocSize)); + IFCOOMFAILFAST(*ppReturnValue); + ZeroMemory(*ppReturnValue, allocSize); + for (UINT i = 0; i < nCount; i++) + { + ctl.ComPtr spItemPeerAsAP; + ctl.ComPtr spProvider; + ctl.ComPtr spChild; + IFC_RETURN(spChildren.GetAt(i, &spChild)); + + IFC_RETURN(spChild.Cast().GetOrCreateAutomationPeer(&spItemPeerAsAP)); + IFC_RETURN(ProviderFromPeer(spItemPeerAsAP.Get(), &spProvider)); + (*ppReturnValue)[i] = spProvider.Detach(); + } + *pReturnValueCount = nCount; + } + } + + return S_OK; +} + +_Check_return_ HRESULT CalendarViewAutomationPeer.GetRowHeadersImpl(out UINT* pReturnValueCount, _Out_writes_to_ptr_(*pReturnValueCount) xaml_automation.Provider.IIRawElementProviderSimple*** ppReturnValue) +{ + *pReturnValueCount = 0; + return S_OK; +} + + +_Check_return_ HRESULT CalendarViewAutomationPeer.GetItemImpl( INT row, INT column, _Outptr_ xaml_automation.Provider.IIRawElementProviderSimple** ppReturnValue) +{ + *ppReturnValue = null; + + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + ctl.ComPtr spHost; + IFC_RETURN(spOwner.Cast().GetActiveGeneratorHost(&spHost)); + + ctl.ComPtr spItemAsI; + ctl.ComPtr spItem; + ctl.ComPtr spItemPeerAsAP; + ctl.ComPtr spProvider; + + int colCount = 0; + IFC_RETURN(get_ColumnCountImpl(&colCount)); + + int firstVisibleIndex = 0; + CalendarPanel* pCalendarPanel = spHost.GetPanel(); + if (pCalendarPanel) + { + IFC_RETURN(pCalendarPanel.get_FirstVisibleIndex(&firstVisibleIndex)); + + int itemIndex = firstVisibleIndex + row * colCount + column; + // firstVisibleIndex is on the first row, we need to count how many space before the firstVisibleIndex. + if (firstVisibleIndex < colCount) + { + int startIndex = 0; + IFC_RETURN(pCalendarPanel.get_StartIndex(&startIndex)); + itemIndex -= startIndex; + } + + if (itemIndex >= 0) + { + IFC_RETURN(pCalendarPanel.ContainerFromIndex(itemIndex, &spItemAsI)); + // This can be a virtualized item or item does not exist, check for null + if (spItemAsI) + { + IFC_RETURN(spItemAsI.As(&spItem)); + + IFC_RETURN(spItem.GetOrCreateAutomationPeer(&spItemPeerAsAP)); + IFC_RETURN(ProviderFromPeer(spItemPeerAsAP.Get(), &spProvider)); + *ppReturnValue = spProvider.Detach(); + } + } + } + return S_OK; +} + +_Check_return_ HRESULT CalendarViewAutomationPeer.RaiseSelectionEvents( xaml_controls.ICalendarViewSelectedDatesChangedEventArgs* pSelectionChangedEventArgs) +{ + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + xaml_controls.CalendarViewDisplayMode mode = xaml_controls.CalendarViewDisplayMode_Month; + IFC_RETURN(spOwner.Cast().get_DisplayMode(&mode)); + + // No header for year or decade view + if (mode == xaml_controls.CalendarViewDisplayMode_Month) + { + ctl.ComPtr spHost; + IFC_RETURN(spOwner.Cast().GetActiveGeneratorHost(&spHost)); + + var pPanel = spHost.GetPanel(); + if (pPanel) + { + wf.DateTime date; + ctl.ComPtr spItemAsI; + ctl.ComPtr spItem; + ctl.ComPtr spItemPeerAsAP; + int itemIndex = 0; + unsigned selectedCount = 0; + + IFC_RETURN(spOwner.Cast().m_tpSelectedDates.get_Size(&selectedCount)); + + ctl.ComPtr> spAddedDates; + ctl.ComPtr> spRemovedDates; + unsigned addedDatesSize = 0; + unsigned removedDatesSize = 0; + + IFC_RETURN(pSelectionChangedEventArgs.get_AddedDates(spAddedDates.ReleaseAndGetAddressOf())); + IFC_RETURN(spAddedDates.get_Size(&addedDatesSize)); + + IFC_RETURN(pSelectionChangedEventArgs.get_RemovedDates(spRemovedDates.ReleaseAndGetAddressOf())); + IFC_RETURN(spRemovedDates.get_Size(&removedDatesSize)); + + // One selection added and that is the only selection + if (addedDatesSize == 1 && selectedCount == 1) + { + + IFC_RETURN(spAddedDates.GetAt(0, &date)); + IFC_RETURN(spHost.CalculateOffsetFromMinDate(date, &itemIndex)); + IFC_RETURN(pPanel.ContainerFromIndex(itemIndex, &spItemAsI)); + if (spItemAsI) + { + IFC_RETURN(spItemAsI.As(&spItem)); + IFC_RETURN(spItem.GetOrCreateAutomationPeer(&spItemPeerAsAP)); + if (spItemPeerAsAP) + { + IFC_RETURN(spItemPeerAsAP.RaiseAutomationEvent(xaml_automation_peers.AutomationEvents_SelectionItemPatternOnElementSelected)); + } + } + + if (removedDatesSize == 1) + { + IFC_RETURN(spRemovedDates.GetAt(0, &date)); + IFC_RETURN(spHost.CalculateOffsetFromMinDate(date, &itemIndex)); + IFC_RETURN(pPanel.ContainerFromIndex(itemIndex, &spItemAsI)); + if (spItemAsI) + { + IFC_RETURN(spItemAsI.As(&spItem)); + IFC_RETURN(spItem.GetOrCreateAutomationPeer(&spItemPeerAsAP)); + if (spItemPeerAsAP) + { + IFC_RETURN(spItemPeerAsAP.RaiseAutomationEvent(xaml_automation_peers.AutomationEvents_SelectionItemPatternOnElementRemovedFromSelection)); + } + } + } + } + else + { + if (addedDatesSize + removedDatesSize > BulkChildrenLimit) + { + IFC_RETURN(RaiseAutomationEvent(xaml_automation_peers.AutomationEvents_SelectionPatternOnInvalidated)); + } + else + { + unsigned i = 0; + for (i = 0; i < addedDatesSize; i++) + { + IFC_RETURN(spAddedDates.GetAt(i, &date)); + IFC_RETURN(spHost.CalculateOffsetFromMinDate(date, &itemIndex)); + IFC_RETURN(pPanel.ContainerFromIndex(itemIndex, &spItemAsI)); + if (spItemAsI) + { + IFC_RETURN(spItemAsI.As(&spItem)); + IFC_RETURN(spItem.GetOrCreateAutomationPeer(&spItemPeerAsAP)); + if (spItemPeerAsAP) + { + IFC_RETURN(spItemPeerAsAP.RaiseAutomationEvent(xaml_automation_peers.AutomationEvents_SelectionItemPatternOnElementAddedToSelection)); + } + } + } + + for (i = 0; i < removedDatesSize; i++) + { + IFC_RETURN(spRemovedDates.GetAt(i, &date)); + IFC_RETURN(spHost.CalculateOffsetFromMinDate(date, &itemIndex)); + IFC_RETURN(pPanel.ContainerFromIndex(itemIndex, &spItemAsI)); + if (spItemAsI) + { + IFC_RETURN(spItemAsI.As(&spItem)); + IFC_RETURN(spItem.GetOrCreateAutomationPeer(&spItemPeerAsAP)); + if (spItemPeerAsAP) + { + IFC_RETURN(spItemPeerAsAP.RaiseAutomationEvent(xaml_automation_peers.AutomationEvents_SelectionItemPatternOnElementRemovedFromSelection)); + } + } + } + } + } + } + } + return S_OK; +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewBaseItemAutomationPeer_Partial.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewBaseItemAutomationPeer_Partial.cs new file mode 100644 index 000000000000..1ad2d6180a1c --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewBaseItemAutomationPeer_Partial.cs @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "precomp.h" +#include "Grid.g.h" +#include "CalendarViewBaseItemAutomationPeer.g.h" +#include "CalendarViewItem.g.h" +#include "CalendarView.g.h" +#include "CalendarViewGeneratorHost.h" +#include "CalendarPanel.g.h" + +using namespace DirectUI; +using namespace DirectUISynonyms; + + +IFACEMETHODIMP CalendarViewBaseItemAutomationPeer.GetPatternCore( xaml_automation_peers.PatternInterface patternInterface, _Outptr_ IInspectable** ppReturnValue) +{ + IFCPTR_RETURN(ppReturnValue); + *ppReturnValue = null; + + bool isItemVisible = false; + + // For the GridItem pattern, make sure the item is visible otherwise we might end up returning a negative row value + // for it. An item may not be visible if it has been scrolled out of view.. + if (patternInterface == xaml_automation_peers.PatternInterface_GridItem && SUCCEEDED(IsItemVisible(isItemVisible)) && isItemVisible || + patternInterface == xaml_automation_peers.PatternInterface_ScrollItem) + { + *ppReturnValue = ctl.as_iinspectable(this); + ctl.addref_interface(this); + } + else + { + IFC_RETURN(CalendarViewBaseItemAutomationPeerGenerated.GetPatternCore(patternInterface, ppReturnValue)); + } + return S_OK; +} + +IFACEMETHODIMP CalendarViewBaseItemAutomationPeer.GetNameCore(out HSTRING* returnValue) +{ + IFCPTR_RETURN(returnValue); + IFC_RETURN(CalendarViewBaseItemAutomationPeerGenerated.GetNameCore(returnValue)); + if (*returnValue == null) + { + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + IFC_RETURN(spOwner.Cast().GetMainText(returnValue)); + } + return S_OK; +} + +_Check_return_ HRESULT CalendarViewBaseItemAutomationPeer.get_ColumnSpanImpl(out INT* pValue) +{ + IFCPTR_RETURN(pValue); + *pValue = 1; + return S_OK; +} + +_Check_return_ HRESULT CalendarViewBaseItemAutomationPeer.get_ContainingGridImpl(_Outptr_result_maybenull_ xaml_automation.Provider.IIRawElementProviderSimple** ppValue) +{ + IFCPTR_RETURN(ppValue); + *ppValue = FALSE; + + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + ctl.ComPtr spAutomationPeer; + CalendarView *pParent = spOwner.Cast().GetParentCalendarView(); + IFCPTR_RETURN(pParent); + + IFC_RETURN(pParent.GetOrCreateAutomationPeer(&spAutomationPeer)); + IFC_RETURN(ProviderFromPeer(spAutomationPeer.Get(), ppValue)); + return S_OK; +} + +_Check_return_ HRESULT CalendarViewBaseItemAutomationPeer.get_RowSpanImpl(out INT* pValue) +{ + IFCPTR_RETURN(pValue); + *pValue = 1; + return S_OK; +} + +// Methods. + +_Check_return_ HRESULT CalendarViewBaseItemAutomationPeer.ScrollIntoViewImpl() +{ + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + wf.DateTime date; + IFC_RETURN(spOwner.Cast().GetDate(&date)); + + CalendarView *pParent = spOwner.Cast().GetParentCalendarView(); + IFCPTR_RETURN(pParent); + + IFC_RETURN(pParent.SetDisplayDate(date)); + + return S_OK; +} + +_Check_return_ HRESULT CalendarViewBaseItemAutomationPeer.IsItemVisible(bool& isVisible) +{ + isVisible = false; + + ctl.ComPtr owner; + IFC_RETURN(get_Owner(&owner)); + + var parent = owner.Cast().GetParentCalendarView(); + + ctl.ComPtr host; + IFC_RETURN(parent.GetActiveGeneratorHost(&host)); + + var calendarPanel = host.GetPanel(); + if (calendarPanel) + { + wf.DateTime date = {}; + IFC_RETURN(owner.Cast().GetDate(&date)); + + int itemIndex = 0; + IFC_RETURN(host.CalculateOffsetFromMinDate(date, &itemIndex)); + + int firstVisibleIndex = 0; + IFC_RETURN(calendarPanel.get_FirstVisibleIndex(&firstVisibleIndex)); + + int lastVisibleIndex = 0; + IFC_RETURN(calendarPanel.get_LastVisibleIndex(&lastVisibleIndex)); + + isVisible = (itemIndex >= firstVisibleIndex && itemIndex <= lastVisibleIndex); + } + + return S_OK; +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewBaseItem_Partial.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewBaseItem_Partial.cs new file mode 100644 index 000000000000..2035e5eda4a3 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewBaseItem_Partial.cs @@ -0,0 +1,398 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "precomp.h" +#include "calendarviewbaseitem.g.h" +#include "CalendarView.g.h" + +using namespace DirectUI; +using namespace DirectUISynonyms; + +// Called when the user presses a pointer down over the CalendarViewBaseItem. +IFACEMETHODIMP CalendarViewBaseItem.OnPointerPressed( + IPointerRoutedEventArgs* pArgs) +{ + HRESULT hr = S_OK; + BOOLEAN isHandled = FALSE; + + IFC(CalendarViewBaseItemGenerated.OnPointerPressed(pArgs)); + + IFC(pArgs.get_Handled(&isHandled)); + if (!isHandled) + { + IFC(SetIsPressed(true)); + IFC(UpdateVisualStateInternal()); + } + +Cleanup: + return hr; +} + +// Called when the user releases a pointer over the CalendarViewBaseItem. +IFACEMETHODIMP CalendarViewBaseItem.OnPointerReleased( + xaml_input.IPointerRoutedEventArgs* pArgs) +{ + HRESULT hr = S_OK; + BOOLEAN isHandled = FALSE; + + IFC(CalendarViewBaseItemGenerated.OnPointerReleased(pArgs)); + + IFC(pArgs.get_Handled(&isHandled)); + if (!isHandled) + { + IFC(SetIsPressed(false)); + IFC(UpdateVisualStateInternal()); + } + +Cleanup: + return hr; +} + +// Called when a pointer enters a CalendarViewBaseItem. +IFACEMETHODIMP CalendarViewBaseItem.OnPointerEntered( + IPointerRoutedEventArgs* pArgs) +{ + HRESULT hr = S_OK; + ctl.ComPtr spPointer; + wdei.PointerDeviceType pointerDeviceType = wdei.PointerDeviceType_Touch; + + IFC(CalendarViewBaseItemGenerated.OnPointerEntered(pArgs)); + + // Only update hover state if the pointer type isn't touch + IFC(pArgs.get_Pointer(&spPointer)); + IFCPTR(spPointer); + IFC(spPointer.get_PointerDeviceType(&pointerDeviceType)); + if (pointerDeviceType != wdei.PointerDeviceType_Touch) + { + IFC(SetIsHovered(true)); + IFC(UpdateVisualStateInternal()); + } + +Cleanup: + return hr; +} + +// Called when a pointer leaves a CalendarViewBaseItem. +IFACEMETHODIMP CalendarViewBaseItem.OnPointerExited( + IPointerRoutedEventArgs* pArgs) +{ + HRESULT hr = S_OK; + + IFC(CalendarViewBaseItemGenerated.OnPointerExited(pArgs)); + + IFC(SetIsHovered(false)); + IFC(SetIsPressed(false)); + IFC(UpdateVisualStateInternal()); + +Cleanup: + return hr; + +} + +// Called when the CalendarViewBaseItem or its children lose pointer capture. +IFACEMETHODIMP CalendarViewBaseItem.OnPointerCaptureLost( + xaml_input.IPointerRoutedEventArgs* pArgs) +{ + HRESULT hr = S_OK; + + IFC(CalendarViewBaseItemGenerated.OnPointerCaptureLost(pArgs)); + + IFC(SetIsHovered(false)); + IFC(SetIsPressed(false)); + IFC(UpdateVisualStateInternal()); + +Cleanup: + return hr; +} + +// Called when the CalendarViewBaseItem receives focus. +IFACEMETHODIMP CalendarViewBaseItem.OnGotFocus( + IRoutedEventArgs* pArgs) +{ + HRESULT hr = S_OK; + xaml.FocusState focusState = xaml.FocusState_Unfocused; + + IFC(CalendarViewBaseItemGenerated.OnGotFocus(pArgs)); + + if (var pCalendarView = GetParentCalendarView()) + { + IFC(pCalendarView.OnItemFocused(this)); + } + + IFC(get_FocusState(&focusState)); + + IFC(SetIsKeyboardFocused(focusState == xaml.FocusState_Keyboard)); + + +Cleanup: + return hr; +} + +// Called when the CalendarViewBaseItem loses focus. +IFACEMETHODIMP CalendarViewBaseItem.OnLostFocus( + IRoutedEventArgs* pArgs) +{ + HRESULT hr = S_OK; + + IFC(CalendarViewBaseItemGenerated.OnLostFocus(pArgs)); + + // remove keyboard focused state + IFC(SetIsKeyboardFocused(false)); + +Cleanup: + return hr; +} + +IFACEMETHODIMP CalendarViewBaseItem.OnRightTapped( + IRightTappedRoutedEventArgs* pArgs) +{ + IFC_RETURN(CalendarViewBaseItemGenerated.OnRightTapped(pArgs)); + + BOOLEAN isHandled = FALSE; + + IFC_RETURN(pArgs.get_Handled(&isHandled)); + + if (!isHandled) + { + bool ignored = false; + IFC_RETURN(FocusSelfOrChild(xaml.FocusState.FocusState_Pointer, &ignored)); + IFC_RETURN(pArgs.put_Handled(TRUE)); + } + return S_OK; +} + +_Check_return_ HRESULT CalendarViewBaseItem.OnIsEnabledChanged( IsEnabledChangedEventArgs* pArgs) +{ + return UpdateTextBlockForeground(); +} + +_Check_return_ HRESULT CalendarViewBaseItem.EnterImpl( + XBOOL bLive, + XBOOL bSkipNameRegistration, + XBOOL bCoercedIsEnabled, + XBOOL bUseLayoutRounding) +{ + IFC_RETURN(CalendarViewBaseItemGenerated.EnterImpl(bLive, bSkipNameRegistration, bCoercedIsEnabled, bUseLayoutRounding)); + + if (bLive) + { + // In case any of the TextBlock properties have been updated while + // we were out of the visual tree, we should update them in order to ensure + // that we always have the most up-to-date values. + // An example where this can happen is if the theme changes while + // the flyout holding the CalendarView for a CalendarDatePicker is closed. + IFC_RETURN(UpdateTextBlockForeground()); + IFC_RETURN(UpdateTextBlockFontProperties()); + IFC_RETURN(UpdateTextBlockAlignments()); + IFC_RETURN(UpdateVisualStateInternal()); + } + + return S_OK; +} + +void CalendarViewBaseItem.SetParentCalendarView( CalendarView* pCalendarView) +{ + m_pParentCalendarView = pCalendarView; + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + + pChrome.SetOwner(pCalendarView ? (CCalendarView*)(pCalendarView.GetHandle()) : null); +} + +CalendarView* CalendarViewBaseItem.GetParentCalendarView() +{ + return m_pParentCalendarView; +} + +_Check_return_ HRESULT CalendarViewBaseItem.UpdateMainText( HSTRING mainText) +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + return pChrome.UpdateMainText(mainText); +} + +_Check_return_ HRESULT CalendarViewBaseItem.UpdateLabelText( HSTRING labelText) +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + return pChrome.UpdateLabelText(labelText); +} + +_Check_return_ HRESULT CalendarViewBaseItem.ShowLabelText( bool showLabel) +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + return pChrome.ShowLabelText(showLabel); +} + +_Check_return_ HRESULT CalendarViewBaseItem.GetMainText(out HSTRING* pMainText) +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + return pChrome.GetMainText(pMainText); +} + +_Check_return_ HRESULT CalendarViewBaseItem.SetIsToday( bool state) +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + return pChrome.SetIsToday(state); +} + +_Check_return_ HRESULT CalendarViewBaseItem.SetIsKeyboardFocused( bool state) +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + return pChrome.SetIsKeyboardFocused(state); +} + +_Check_return_ HRESULT CalendarViewBaseItem.SetIsSelected( bool state) +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + return pChrome.SetIsSelected(state); +} + +_Check_return_ HRESULT CalendarViewBaseItem.SetIsBlackout( bool state) +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + return pChrome.SetIsBlackout(state); +} + +_Check_return_ HRESULT CalendarViewBaseItem.SetIsHovered( bool state) +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + return pChrome.SetIsHovered(state); +} + +_Check_return_ HRESULT CalendarViewBaseItem.SetIsPressed( bool state) +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + return pChrome.SetIsPressed(state); +} + +_Check_return_ HRESULT CalendarViewBaseItem.SetIsOutOfScope( bool state) +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + return pChrome.SetIsOutOfScope(state); +} + +// If this item is unfocused, sets focus on the CalendarViewBaseItem. +// Otherwise, sets focus to whichever element currently has focus +// (so focusState can be propagated). +_Check_return_ HRESULT CalendarViewBaseItem.FocusSelfOrChild( + xaml.FocusState focusState, + out bool* pFocused, + xaml_input.FocusNavigationDirection focusNavigationDirection) +{ + HRESULT hr = S_OK; + BOOLEAN isItemAlreadyFocused = FALSE; + ctl.ComPtr spItemToFocus = NULL; + + *pFocused = false; + + IFC(HasFocus(&isItemAlreadyFocused)); + if (isItemAlreadyFocused) + { + // Re-focus the currently focused item to propagate focusState (the item might be focused + // under a different FocusState value). + IFC(GetFocusedElement(&spItemToFocus)); + } + else + { + spItemToFocus = this; + } + + if (spItemToFocus) + { + BOOLEAN focused = FALSE; + IFC(SetFocusedElementWithDirection(spItemToFocus.Get(), focusState, FALSE /*animateIfBringIntoView*/, &focused, focusNavigationDirection)); + *pFocused = !!focused; + } + +Cleanup: + return hr; +} + +#if DBG +// wf.DateTime has an int64 member which is not intutive enough. This method will convert it +// into numbers that we can easily read. +_Check_return_ HRESULT CalendarViewBaseItem.SetDateForDebug( wf.DateTime value) +{ + HRESULT hr = S_OK; + + var pCalendarView = GetParentCalendarView(); + if (pCalendarView) + { + var pCalendar = pCalendarView.GetCalendar(); + IFC(pCalendar.SetDateTime(value)); + IFC(pCalendar.get_Era(&m_eraForDebug)); + IFC(pCalendar.get_Year(&m_yearForDebug)); + IFC(pCalendar.get_Month(&m_monthForDebug)); + IFC(pCalendar.get_Day(&m_dayForDebug)); + } + +Cleanup: + return hr; +} +#endif + + +_Check_return_ HRESULT CalendarViewBaseItem.InvalidateRender() +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + pChrome.InvalidateRender(); + return S_OK; +} + +_Check_return_ HRESULT CalendarViewBaseItem.UpdateTextBlockForeground() +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + return pChrome.UpdateTextBlocksForeground(); +} + +_Check_return_ HRESULT CalendarViewBaseItem.UpdateTextBlockFontProperties() +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + return pChrome.UpdateTextBlocksFontProperties(); +} + +_Check_return_ HRESULT CalendarViewBaseItem.UpdateTextBlockAlignments() +{ + CCalendarViewBaseItemChrome* pChrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + return pChrome.UpdateTextBlocksAlignments(); +} + +// Change to the correct visual state for the CalendarViewBaseItem. +_Check_return_ HRESULT CalendarViewBaseItem.ChangeVisualState( + // true to use transitions when updating the visual state, false + // to snap directly to the new visual state. + bool bUseTransitions) +{ + IFC_RETURN(CalendarViewBaseItemGenerated.ChangeVisualState(bUseTransitions)); + + CCalendarViewBaseItemChrome* chrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + BOOLEAN ignored = FALSE; + BOOLEAN isPointerOver = chrome.IsHovered(); + BOOLEAN isPressed = chrome.IsPressed(); + + // Common States Group + if (isPressed) + { + IFC_RETURN(GoToState(bUseTransitions, "Pressed", &ignored)); + } + else if (isPointerOver) + { + IFC_RETURN(GoToState(bUseTransitions, "PointerOver", &ignored)); + } + else + { + IFC_RETURN(GoToState(bUseTransitions, "Normal", &ignored)); + } + + return S_OK; +} + +_Check_return_ HRESULT CalendarViewBaseItem.UpdateVisualStateInternal() +{ + CCalendarViewBaseItemChrome* chrome = (CCalendarViewBaseItemChrome*)(GetHandle()); + if (chrome.HasTemplateChild()) // If !HasTemplateChild, then there is no visual in ControlTemplate for CalendarViewDayItemStyle + // There should be no VisualStateGroup defined, so ignore UpdateVisualState + { + IFC_RETURN(UpdateVisualState(FALSE /* fUseTransitions */)); + } + + return S_OK; +} \ No newline at end of file diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewDayItemAutomationPeer_Partial.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewDayItemAutomationPeer_Partial.cs new file mode 100644 index 000000000000..cf2391b6ac3f --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewDayItemAutomationPeer_Partial.cs @@ -0,0 +1,321 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "precomp.h" +#include "Grid.g.h" +#include "CalendarViewDayItemAutomationPeer.g.h" +#include "CalendarViewDayItem.g.h" +#include "CalendarView.g.h" +#include "CalendarViewGeneratorHost.h" +#include "CalendarPanel.g.h" + +using namespace DirectUI; +using namespace DirectUISynonyms; + +IFACEMETHODIMP CalendarViewDayItemAutomationPeer.GetPatternCore( xaml_automation_peers.PatternInterface patternInterface, _Outptr_ IInspectable** ppReturnValue) +{ + IFCPTR_RETURN(ppReturnValue); + *ppReturnValue = null; + + if (patternInterface == xaml_automation_peers.PatternInterface_TableItem || + patternInterface == xaml_automation_peers.PatternInterface_SelectionItem) + { + *ppReturnValue = ctl.as_iinspectable(this); + ctl.addref_interface(this); + } + else + { + IFC_RETURN(CalendarViewDayItemAutomationPeerGenerated.GetPatternCore(patternInterface, ppReturnValue)); + } + return S_OK; +} + +IFACEMETHODIMP CalendarViewDayItemAutomationPeer.GetAutomationControlTypeCore(out xaml_automation_peers.AutomationControlType* pReturnValue) +{ + IFCPTR_RETURN(pReturnValue); + *pReturnValue = xaml_automation_peers.AutomationControlType_DataItem; + + return S_OK; +} + +IFACEMETHODIMP CalendarViewDayItemAutomationPeer.GetClassNameCore(out HSTRING* pReturnValue) +{ + IFCPTR_RETURN(pReturnValue); + IFC_RETURN(wrl_wrappers.Hstring(STR_LEN_PAIR("CalendarViewDayItem")).CopyTo(pReturnValue)); + return S_OK; +} + +IFACEMETHODIMP CalendarViewDayItemAutomationPeer.IsEnabledCore(out BOOLEAN* pReturnValue) +{ + IFCPTR_RETURN(pReturnValue); + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + BOOLEAN isBlackout = FALSE; + IFC_RETURN(spOwner.Cast().get_IsBlackout(&isBlackout)); + + // This property also takes in consideration of a day is 'blackout' or not. Blackout dates are disabled + if (isBlackout) + { + *pReturnValue = FALSE; + } + else + { + IFC_RETURN(CalendarViewDayItemAutomationPeerGenerated.IsEnabledCore(pReturnValue)); + } + return S_OK; +} + +_Check_return_ HRESULT CalendarViewDayItemAutomationPeer.GetColumnHeaderItemsImpl(out UINT* pReturnValueCount, _Out_writes_to_ptr_(*pReturnValueCount) xaml_automation.Provider.IIRawElementProviderSimple*** ppReturnValue) +{ + *pReturnValueCount = 0; + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + wf.DateTime date; + IFC_RETURN(spOwner.Cast().GetDate(&date)); + + CalendarView *pParent = spOwner.Cast().GetParentCalendarView(); + IFCPTR_RETURN(pParent); + + UINT nCount = 0; + ctl.ComPtr spWeekDayNames; + + // Gets the 'WeekDayNames' part of the container template and find weekindex position as elment + IFC_RETURN(pParent.GetTemplatePart(STR_LEN_PAIR("WeekDayNames"), spWeekDayNames.ReleaseAndGetAddressOf())); + if (spWeekDayNames) + { + ctl.ComPtr> spChildren; + IFC_RETURN(spWeekDayNames.Cast().get_Children(&spChildren)); + IFC_RETURN(spChildren.get_Size(&nCount)); + + int weekindex = 0; + IFC_RETURN(get_ColumnImpl(&weekindex)); + + ctl.ComPtr spItemPeerAsAP; + ctl.ComPtr spProvider; + ctl.ComPtr spChild; + IFC_RETURN(spChildren.GetAt(weekindex, &spChild)); + if (spChild) + { + IFC_RETURN(spChild.Cast().GetOrCreateAutomationPeer(&spItemPeerAsAP)); + if (spItemPeerAsAP) + { + UINT allocSize = sizeof(IIRawElementProviderSimple*); + *ppReturnValue = (IIRawElementProviderSimple**)(CoTaskMemAlloc(allocSize)); + IFCOOMFAILFAST(*ppReturnValue); + ZeroMemory(*ppReturnValue, allocSize); + + IFC_RETURN(ProviderFromPeer(spItemPeerAsAP.Get(), &spProvider)); + (*ppReturnValue)[0] = spProvider.Detach(); + *pReturnValueCount = 1; + } + } + } + return S_OK; +} + +_Check_return_ HRESULT CalendarViewDayItemAutomationPeer.GetRowHeaderItemsImpl(out UINT* pReturnValueCount, _Out_writes_to_ptr_(*pReturnValueCount) xaml_automation.Provider.IIRawElementProviderSimple*** ppReturnValue) +{ + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + CalendarViewDayItem* item = spOwner.Cast(); + CalendarView *pParent = item.GetParentCalendarView(); + IFCPTR_RETURN(pParent); + + wf.DateTime itemDate; + IFC_RETURN(item.GetDate(&itemDate)); + + IFC_RETURN(pParent.GetRowHeaderForItemAutomationPeer(itemDate, xaml_controls.CalendarViewDisplayMode_Month, pReturnValueCount, ppReturnValue)); + + return S_OK; +} + +_Check_return_ HRESULT CalendarViewDayItemAutomationPeer.get_SelectionContainerImpl(_Outptr_result_maybenull_ xaml_automation.Provider.IIRawElementProviderSimple** ppValue) +{ + return get_ContainingGridImpl(ppValue); +} + +_Check_return_ HRESULT CalendarViewDayItemAutomationPeer.AddToSelectionImpl() +{ + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + wf.DateTime date; + IFC_RETURN(spOwner.Cast().GetDate(&date)); + + CalendarView *pParent = spOwner.Cast().GetParentCalendarView(); + IFCPTR_RETURN(pParent); + + bool isSelected = false; + IFC_RETURN(pParent.IsSelected(date, &isSelected)); + if (!isSelected) + { + IFC_RETURN(pParent.OnSelectDayItem(spOwner.Cast())); + } + + return S_OK; +} + +_Check_return_ HRESULT CalendarViewDayItemAutomationPeer.RemoveFromSelectionImpl() +{ + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + wf.DateTime date; + IFC_RETURN(spOwner.Cast().GetDate(&date)); + + CalendarView *pParent = spOwner.Cast().GetParentCalendarView(); + IFCPTR_RETURN(pParent); + + bool isSelected = false; + IFC_RETURN(pParent.IsSelected(date, &isSelected)); + if (isSelected) + { + IFC_RETURN(pParent.OnSelectDayItem(spOwner.Cast())); + } + + return S_OK; +} + +_Check_return_ HRESULT CalendarViewDayItemAutomationPeer.get_IsSelectedImpl(out BOOLEAN* pValue) +{ + IFCPTR_RETURN(pValue); + *pValue = FALSE; + + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + wf.DateTime date; + IFC_RETURN(spOwner.Cast().GetDate(&date)); + bool isSelected = false; + CalendarView *pParent = spOwner.Cast().GetParentCalendarView(); + IFCPTR_RETURN(pParent); + + IFC_RETURN(pParent.IsSelected(date, &isSelected)); + if (isSelected) + { + *pValue = TRUE; + } + return S_OK; +} + +_Check_return_ HRESULT CalendarViewDayItemAutomationPeer.SelectImpl() +{ + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + wf.DateTime date; + IFC_RETURN(spOwner.Cast().GetDate(&date)); + + CalendarView *pParent = spOwner.Cast().GetParentCalendarView(); + IFCPTR_RETURN(pParent); + + ctl.ComPtr> spSelectedItems; + IFC_RETURN(pParent.get_SelectedDates(&spSelectedItems)); + IFC_RETURN(spSelectedItems.Clear()); + + IFC_RETURN(pParent.OnSelectDayItem(spOwner.Cast())); + + return S_OK; +} + +// calculate visible column from index of the item +_Check_return_ HRESULT CalendarViewDayItemAutomationPeer.get_ColumnImpl(out INT* pValue) +{ + IFCPTR_RETURN(pValue); + *pValue = 0; + + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + wf.DateTime date; + IFC_RETURN(spOwner.Cast().GetDate(&date)); + + CalendarView *pParent = spOwner.Cast().GetParentCalendarView(); + IFCPTR_RETURN(pParent); + + ctl.ComPtr spHost; + IFC_RETURN(pParent.GetActiveGeneratorHost(&spHost)); + + CalendarPanel* pCalendarPanel = spHost.GetPanel(); + if (pCalendarPanel) + { + int itemIndex = 0; + + // Get realized item index + IFC_RETURN(spHost.CalculateOffsetFromMinDate(date, &itemIndex)); + + int cols = 1; + IFC_RETURN(pCalendarPanel.get_Cols(&cols)); + int firstVisibleIndex = 0; + IFC_RETURN(pCalendarPanel.get_FirstVisibleIndex(&firstVisibleIndex)); + + // Calculate the relative positon w.r.to the visible elements from item index + int relativePos = (itemIndex - firstVisibleIndex); + if (firstVisibleIndex < cols) + { + int monthViewStartIndex = 0; + IFC_RETURN(pCalendarPanel.get_StartIndex(&monthViewStartIndex)); + relativePos += monthViewStartIndex; + } + + *pValue = relativePos % cols; + if (*pValue < 0) + { + *pValue += cols; + } + } + return S_OK; +} + +_Check_return_ HRESULT CalendarViewDayItemAutomationPeer.get_RowImpl(out INT* pValue) +{ + IFCPTR_RETURN(pValue); + *pValue = 0; + + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + wf.DateTime date; + IFC_RETURN(spOwner.Cast().GetDate(&date)); + + CalendarView *pParent = spOwner.Cast().GetParentCalendarView(); + IFCPTR_RETURN(pParent); + + ctl.ComPtr spHost; + IFC_RETURN(pParent.GetActiveGeneratorHost(&spHost)); + + CalendarPanel* pCalendarPanel = spHost.GetPanel(); + if (pCalendarPanel) + { + int itemIndex = 0; + IFC_RETURN(spHost.CalculateOffsetFromMinDate(date, &itemIndex)); + + int cols = 1; + IFC_RETURN(pCalendarPanel.get_Cols(&cols)); + int firstVisibleIndex = 0; + IFC_RETURN(pCalendarPanel.get_FirstVisibleIndex(&firstVisibleIndex)); + + wg.DayOfWeek firstDayOfWeek = wg.DayOfWeek_Sunday; + IFC_RETURN(pParent.get_FirstDayOfWeek(&firstDayOfWeek)); + + // Find the relative row position w.r.to visible rows + int relativePos = (itemIndex - firstVisibleIndex); + if (firstVisibleIndex < cols) + { + int monthViewStartIndex = 0; + IFC_RETURN(pCalendarPanel.get_StartIndex(&monthViewStartIndex)); + relativePos += monthViewStartIndex; + } + + *pValue = relativePos / cols; + // the element is not visible and we can't define row + if (*pValue < 0) + { + return E_NOT_SUPPORTED; + } + } + return S_OK; +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewDayItemChangingEventArgs_Partial.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewDayItemChangingEventArgs_Partial.cs new file mode 100644 index 000000000000..8c6c0cf0f915 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewDayItemChangingEventArgs_Partial.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "precomp.h" +#include "CalendarViewDayItemChangingEventArgs.g.h" + +using namespace DirectUI; +using namespace DirectUISynonyms; + + +CalendarViewDayItemChangingEventArgs.CalendarViewDayItemChangingEventArgs() + : m_pContainerBackPointer(null) +{ +} + + +// reset the args in such a way that it is not holding on to any other structures +_Check_return_ HRESULT CalendarViewDayItemChangingEventArgs.ResetLifetimeImpl() +{ + HRESULT hr = S_OK; + + IFC(put_Callback(null)); + IFC(put_Item(null)); +Cleanup: + return hr; +} + +// Register this container for a callback on the next phase +_Check_return_ HRESULT CalendarViewDayItemChangingEventArgs.RegisterUpdateCallbackImpl( wf.ITypedEventHandler* pCallback) +{ + return RegisterUpdateCallbackWithPhaseImpl(m_phase + 1, pCallback); +} + +// Register this container for a callback on a specific phase +_Check_return_ HRESULT CalendarViewDayItemChangingEventArgs.RegisterUpdateCallbackWithPhaseImpl( UINT callbackPhase, wf.ITypedEventHandler* pCallback) +{ + HRESULT hr = S_OK; + + m_phase = callbackPhase; + IFC(put_Callback(pCallback)); + IFC(put_WantsCallBack(TRUE)); + +Cleanup: + return hr; +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewDayItem_Partial.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewDayItem_Partial.cs new file mode 100644 index 000000000000..e53c83cae44f --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewDayItem_Partial.cs @@ -0,0 +1,169 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "precomp.h" +#include "calendarviewdayitem.g.h" +#include "CalendarViewDayItemChangingEventArgs.g.h" +#include "CalendarView.g.h" +#include "CalendarViewDayItemAutomationPeer.g.h" + +using namespace DirectUI; +using namespace DirectUISynonyms; + + +// Handle the custom property changed event and call the OnPropertyChanged methods. +_Check_return_ HRESULT CalendarViewDayItem.OnPropertyChanged2( + const PropertyChangedParams& args) +{ + HRESULT hr = S_OK; + + IFC(CalendarViewDayItemGenerated.OnPropertyChanged2(args)); + + if (args.m_pDP.GetIndex() == KnownPropertyIndex.CalendarViewDayItem_IsBlackout) + { + bool isBlackout = false; + + IFC(args.m_pNewValue.GetBool(isBlackout)); + + IFC(SetIsBlackout(isBlackout)); + + // when setting an item to blackout, we need remove it from selectedDates (if it exists) + if (isBlackout) + { + ctl.ComPtr spParentCalendarView(GetParentCalendarView()); + if (spParentCalendarView) + { + IFC(spParentCalendarView.OnDayItemBlackoutChanged(this, isBlackout)); + } + } + } + +Cleanup: + return hr; +} + +_Check_return_ HRESULT CalendarViewDayItem.SetDensityColorsImpl( wfc.IEnumerable* pColors) +{ + return (CCalendarViewBaseItemChrome*)(GetHandle()).SetDensityColors(pColors); +} + +_Check_return_ HRESULT CalendarViewDayItem.GetBuildTreeArgs(out ctl.ComPtr* pspArgs) +{ + HRESULT hr = S_OK; + + if (!m_tpBuildTreeArgs) + { + ctl.ComPtr spArgs; + + IFC(ctl.make(&spArgs)); + SetPtrValue(m_tpBuildTreeArgs, spArgs.Get()); + *pspArgs = std.move(spArgs); + } + else + { + *pspArgs = m_tpBuildTreeArgs.Get(); + } + +Cleanup: + return hr; +} + + + +// Called when a pointer makes a tap gesture on a CalendarViewBaseItem. +IFACEMETHODIMP CalendarViewDayItem.OnTapped( + ITappedRoutedEventArgs* pArgs) +{ + HRESULT hr = S_OK; + BOOLEAN isHandled = FALSE; + + IFC(CalendarViewDayItemGenerated.OnTapped(pArgs)); + + IFC(pArgs.get_Handled(&isHandled)); + + if (!isHandled) + { + ctl.ComPtr spParentCalendarView(GetParentCalendarView()); + + if (spParentCalendarView) + { + bool ignored = false; + IFC(FocusSelfOrChild(xaml.FocusState.FocusState_Pointer, &ignored)); + IFC(spParentCalendarView.OnSelectDayItem(this)); + IFC(pArgs.put_Handled(TRUE)); + + ElementSoundPlayerService* soundPlayerService = DXamlCore.GetCurrent().GetElementSoundPlayerServiceNoRef(); + IFC(soundPlayerService.RequestInteractionSoundForElement(xaml.ElementSoundKind_Invoke, this)); + } + } + +Cleanup: + return hr; +} + + +// Handles when a key is pressed down on the CalendarView. +IFACEMETHODIMP CalendarViewDayItem.OnKeyDown( + xaml_input.IKeyRoutedEventArgs* pArgs) +{ + HRESULT hr = S_OK; + BOOLEAN isHandled = FALSE; + + IFC(CalendarViewDayItemGenerated.OnKeyDown(pArgs)); + + IFC(pArgs.get_Handled(&isHandled)); + + if (!isHandled) + { + ctl.ComPtr spParentCalendarView(GetParentCalendarView()); + + if (spParentCalendarView) + { + wsy.VirtualKey key = wsy.VirtualKey_None; + IFC(pArgs.get_Key(&key)); + + if (key == wsy.VirtualKey_Space || key == wsy.VirtualKey_Enter) + { + IFC(spParentCalendarView.OnSelectDayItem(this)); + IFC(pArgs.put_Handled(true)); + IFC(SetIsKeyboardFocused(true)); + + ElementSoundPlayerService* soundPlayerService = DXamlCore.GetCurrent().GetElementSoundPlayerServiceNoRef(); + IFC(soundPlayerService.RequestInteractionSoundForElement(xaml.ElementSoundKind_Invoke, this)); + } + else + { + // let CalendarView handle this event and tell calendarview the event comes from a MonthYearItem + IFC(spParentCalendarView.SetKeyDownEventArgsFromCalendarItem(pArgs)); + } + } + } + +Cleanup: + return hr; +} + +#if DBG +_Check_return_ HRESULT CalendarViewDayItem.put_Date( wf.DateTime value) +{ + HRESULT hr = S_OK; + + IFC(SetDateForDebug(value)); + IFC(CalendarViewDayItemGenerated.put_Date(value)); + +Cleanup: + return hr; +} +#endif + +IFACEMETHODIMP CalendarViewDayItem.OnCreateAutomationPeer(_Outptr_result_maybenull_ xaml_automation_peers.IAutomationPeer** ppAutomationPeer) +{ + IFCPTR_RETURN(ppAutomationPeer); + *ppAutomationPeer = null; + + ctl.ComPtr spAutomationPeer; + IFC_RETURN(ActivationAPI.ActivateAutomationInstance(KnownTypeIndex.CalendarViewDayItemAutomationPeer, GetHandle(), spAutomationPeer.GetAddressOf())); + IFC_RETURN(spAutomationPeer.put_Owner(this)); + *ppAutomationPeer = spAutomationPeer.Detach(); + return S_OK; +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorDecadeViewHost.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorDecadeViewHost.cs new file mode 100644 index 000000000000..80986bffc752 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorDecadeViewHost.cs @@ -0,0 +1,315 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "precomp.h" +#include "CalendarViewGeneratorDecadeViewHost.h" +#include "CalendarView.g.h" +#include "CalendarViewItem.g.h" +#include "DateComparer.h" + +using namespace DirectUI; +using namespace DirectUISynonyms; + +// Work around disruptive max/min macros +#undef max +#undef min + + +_Check_return_ HRESULT GetContainer( + IInspectable* pItem, + xaml.IDependencyObject* pRecycledContainer, + _Outptr_ CalendarViewBaseItem** ppContainer) +{ + HRESULT hr = S_OK; + + ctl.ComPtr spItem; + + IFC(ctl.new CalendarViewItem(&spItem)); + + IFC(spItem.CopyTo(ppContainer)); + +Cleanup: + return hr; +} + +_Check_return_ IFACEMETHODIMP PrepareItemContainer( + xaml.IDependencyObject* pContainer, + IInspectable* pItem) +{ + HRESULT hr = S_OK; + wf.DateTime date; + ctl.ComPtr spContainer; + + IFC(ctl.do_get_value(date, pItem)); + spContainer = (CalendarViewItem*)(pContainer); + IFC(spContainer.put_Date(date)); + IFC(GetCalendar().SetDateTime(date)); + + // main text + { + wrl_wrappers.HString mainText; + + IFC(GetCalendar().YearAsString(mainText.GetAddressOf())); + + + IFC(spContainer.UpdateMainText(mainText.Get())); + } + + // today state will be updated in CalendarViewGeneratorHost.PrepareItemContainer + + // DecadeView doesn't have label, selection state + + // Make a grid effect on DecadeView. + // For MonthView, we put a margin on CalendarViewDayItem in the template to achieve the grid effect. + // For YearView and DecadeView, we can't do the same because there is no template for MonthItem and YearItem + { + xaml.Thickness margin{ 1.0, 1.0, 1.0, 1.0 }; + IFC(spContainer.put_Margin(margin)); + } + + //This code enables the focus visuals on the CalendarViewItems in the Decade Pane in the correct position. + { + const xaml.Thickness focusMargin{ -2.0, -2.0, -2.0, -2.0 }; + IFC(spContainer.put_FocusVisualMargin(focusMargin)); + + IFC(spContainer.put_UseSystemFocusVisuals(TRUE)); + } + + IFC(CalendarViewGeneratorHost.PrepareItemContainer(pContainer, pItem)); + +Cleanup: + return hr; +} + +_Check_return_ HRESULT GetIsFirstItemInScope( int index, out bool* pIsFirstItemInScope) +{ + HRESULT hr = S_OK; + + *pIsFirstItemInScope = false; + if (index == 0) + { + *pIsFirstItemInScope = true; + } + else + { + wf.DateTime date = {}; + int year = 0; + + IFC(GetDateAt(index, &date)); + var pCalendar = GetCalendar(); + IFC(pCalendar.SetDateTime(date)); + IFC(pCalendar.get_Year(&year)); + + *pIsFirstItemInScope = year % s_decade == 0; + + // "Decade" is a virtual scope which should be less than Era and greater than Year. + // So a decade scope should not cross Eras. + // When this year is the first year of this Era, we still look it + // as the first item in the scope. + if (!*pIsFirstItemInScope) + { + int firstYearInThisEra = 0; + IFC(pCalendar.get_FirstYearInThisEra(&firstYearInThisEra)); + *pIsFirstItemInScope = year == firstYearInThisEra; + } + } + +Cleanup: + return hr; +} + +_Check_return_ HRESULT GetUnit(out int* pValue) +{ + return GetCalendar().get_Year(pValue); +} + +_Check_return_ HRESULT SetUnit( int value) +{ + return GetCalendar().put_Year(value); +} + +_Check_return_ HRESULT AddUnits( int value) +{ + return GetCalendar().AddYears(value); +} + +_Check_return_ HRESULT AddScopes( int value) +{ + if (value != 0) + { + var pCalendar = GetCalendar(); + // for the calendars that don't have multiple Eras, we don't need to validate if add scope + // will cause Era changes. + if (!GetOwner().IsMultipleEraCalendar()) + { + // TODO: boundary check + IFC_RETURN(pCalendar.AddYears(value * s_decade)); + } + else + { + // In Windows.Globalization.Calendar, if the result crosses an era when we add a year or a month, + // the result will be adjust to the first day of the new era (last day if it is a substraction). + // Now we introduce a virtual scope "Decade" which doesn't exist in Windows.Globalization.Calendar, + // so when we add/substract a decade, we should follow the same rule as year/month. + + bool goForward = value > 0; + + if (!goForward) + { + value = -value; + } + + while (value-- > 0) + { + int oldEra = 0; + int oldYear = 0; + int lastYearInOldEra = 0; + int newEra = 0; + int newYear = 0; + int newMonth = 0; + int newDay = 0; + + IFC_RETURN(pCalendar.get_Era(&oldEra)); + IFC_RETURN(pCalendar.get_Year(&oldYear)); + IFC_RETURN(pCalendar.get_LastYearInThisEra(&lastYearInOldEra)); + + //when going back from year 10-19, we want to show 1-9, instead of 0-9. + //year 0 will take us to previous era which we don't want to. + if (oldYear == 10 && !goForward) + { + IFC_RETURN(pCalendar.AddYears(-s_decade+1)); + } + else + { + IFC_RETURN(pCalendar.AddYears(goForward ? s_decade : -s_decade)); + } + + IFC_RETURN(pCalendar.get_Era(&newEra)); + + if (oldEra != newEra) + { + if (goForward) + { + // Adjust to the first day of this era if the new range starts in a different era. + // e.g Showa era ends at 64. For range 50-59, if we go forward a decade, the result should be 60-64. + // If we go forward another decade from Showa 60-64, the result should be Heisei 1-10. + if (oldYear + 10 > lastYearInOldEra) + { + IFC_RETURN(pCalendar.get_FirstYearInThisEra(&newYear)); + IFC_RETURN(pCalendar.put_Year(newYear)); + } + } + // Adjust to the last day of this era if the new range starts in a different era. + // Similiar as the example above. If we go back a decade from Heisei 1-10, the result should be Showa 60-64. + // Go back another decade from Showa 60-64, the result should be Showa 50-59. + else if(oldYear < 10) + { + IFC_RETURN(pCalendar.get_LastYearInThisEra(&newYear));; + IFC_RETURN(pCalendar.put_Year(newYear)); + } + + IFC_RETURN(pCalendar.get_FirstMonthInThisYear(&newMonth)); + IFC_RETURN(pCalendar.put_Month(newMonth)); + IFC_RETURN(pCalendar.get_FirstDayInThisMonth(&newDay)); + IFC_RETURN(pCalendar.put_Day(newDay)); + } + } + } + } + + return S_OK; +} + +// the virtual "Decade" scope doesn't exist in globalization calendar, when we adjust to the first/last +// unit in the decade scope, we need to make sure we don't cross the boundaries (Era). +_Check_return_ HRESULT GetFirstUnitInThisScope(out int* pValue) +{ + int year = 0; + int firstYearInThisEra = 0; + *pValue = 0; + + IFC_RETURN(GetCalendar().get_Year(&year)); + *pValue = year - year % s_decade; + + IFC_RETURN(GetCalendar().get_FirstYearInThisEra(&firstYearInThisEra)); + if (*pValue < firstYearInThisEra) + { + *pValue = firstYearInThisEra; + } + + return S_OK; +} + +// the virtual "Decade" scope doesn't exist in globalization calendar, when we adjust to the first/last +// unit in the decade scope, we need to make sure we don't cross the boundaries (Era). +_Check_return_ HRESULT GetLastUnitInThisScope(out int* pValue) +{ + int year = 0; + int lastYearInThisEra = 0; + *pValue = 0; + + IFC_RETURN(GetCalendar().get_Year(&year)); + *pValue = year - year % s_decade + s_decade - 1; + + IFC_RETURN(GetCalendar().get_LastYearInThisEra(&lastYearInThisEra)); + if (*pValue > lastYearInThisEra) + { + *pValue = lastYearInThisEra; + } + + return S_OK; +} + +_Check_return_ HRESULT OnScopeChanged() +{ + wrl_wrappers.HString minYearString; + wrl_wrappers.HString maxYearString; + wrl_wrappers.Hconst string seperator = " - "; + wrl_wrappers.HString tempResult; + + IFC_RETURN(GetCalendar().SetDateTime(m_minDateOfCurrentScope)); + IFC_RETURN(GetCalendar().YearAsString(minYearString.GetAddressOf())); + + IFC_RETURN(GetCalendar().SetDateTime(m_maxDateOfCurrentScope)); + IFC_RETURN(GetCalendar().YearAsString(maxYearString.GetAddressOf())); + + IFC_RETURN(minYearString.Concat(seperator, tempResult)); + IFC_RETURN(tempResult.Concat(maxYearString, m_pHeaderText)); // "YYYY - YYYY" + + return S_OK; +} + +_Check_return_ HRESULT GetPossibleItemStrings(_Outptr_ const std.vector** ppStrings) +{ + *ppStrings = &m_possibleItemStrings; + + // for DecadeView, we couldn't get a list of possible strings because each year has different string. + // the best we could do is just measure only one item (the string of current year) and assume this is + // the biggest item. + if (m_possibleItemStrings.empty()) + { + var pCalendar = GetCalendar(); + + IFC_RETURN(pCalendar.SetToNow()); + + m_possibleItemStrings.reserve(1); + + wrl_wrappers.HString string; + IFC_RETURN(pCalendar.YearAsString(string.GetAddressOf())); + m_possibleItemStrings.emplace_back(std.move(string)); + } + + return S_OK; +} + +_Check_return_ HRESULT CompareDate( wf.DateTime lhs, wf.DateTime rhs, out int* pResult) +{ + return GetOwner().GetDateComparer().CompareYear(lhs, rhs, pResult); +} + +INT64 GetAverageTicksPerUnit() +{ + // this is being used to estimate the distance between two dates, + // it doesn't need to be (and it can't be) the exact value + return CalendarConstants.s_ticksPerDay * 365; +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorHost.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorHost.cs new file mode 100644 index 000000000000..d6b115f683df --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorHost.cs @@ -0,0 +1,707 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "precomp.h" +#include "CalendarViewGeneratorHost.h" +#include "CalendarView.g.h" +#include "CalendarViewItem.g.h" +#include "CalendarViewDayItem_Partial.h" +#include "CalendarViewDayItemChangingEventArgs.g.h" +#include "DateComparer.h" +#include "CalendarPanel.g.h" +#include "ScrollViewer.g.h" + +using namespace DirectUI; +using namespace DirectUISynonyms; + +// Work around disruptive max/min macros +#undef max +#undef min + +// IGeneratorHost + +_Check_return_ IFACEMETHODIMP CalendarViewGeneratorHost.get_View( + _Outptr_ wfc.IList** ppView) +{ + ctl.ComPtr spThis(this); + + return spThis.CopyTo(ppView); +} + +_Check_return_ IFACEMETHODIMP CalendarViewGeneratorHost.get_CollectionView( + _Outptr_ xaml_data.ICollectionView** ppCollectionView) +{ + // return null so MCBP knows there is no group. + *ppCollectionView = null; + + return S_OK; +} + +_Check_return_ IFACEMETHODIMP IsItemItsOwnContainer( + IInspectable* pItem, + out BOOLEAN* pIsOwnContainer) +{ + // our item is DateTime, not the container + *pIsOwnContainer = false; + return S_OK; +} + +_Check_return_ IFACEMETHODIMP GetContainerForItem( + IInspectable* pItem, + xaml.IDependencyObject* pRecycledContainer, + _Outptr_ xaml.IDependencyObject** ppContainer) +{ + HRESULT hr = S_OK; + ctl.ComPtr spContainer; + + IFC(GetContainer(pItem, pRecycledContainer, &spContainer)); + spContainer.SetParentCalendarView(GetOwner()); + + IFC(spContainer.MoveTo(ppContainer)); + +Cleanup: + return hr; +} + +_Check_return_ IFACEMETHODIMP PrepareItemContainer( + xaml.IDependencyObject* pContainer, + IInspectable* pItem) +{ + // All calendar items have same scope logical, handle it here: + ctl.ComPtr spContainer((CalendarViewItem*)(pContainer)); + + IFC_RETURN(spContainer.SetIsOutOfScope(false)); + + // today state + { + wf.DateTime date; + bool isToday = false; + int result = 0; + + IFC_RETURN(ctl.do_get_value(date, pItem)); + + IFC_RETURN(CompareDate(date, GetOwner().GetToday(), &result)); + if (result == 0) + { + BOOLEAN isTodayHighlighted = FALSE; + + IFC_RETURN(GetOwner().get_IsTodayHighlighted(&isTodayHighlighted)); + + isToday = !!isTodayHighlighted; + } + + IFC_RETURN(spContainer.SetIsToday(isToday)); + } + + return S_OK; +} + +_Check_return_ IFACEMETHODIMP ClearContainerForItem( + xaml.IDependencyObject* pContainer, + IInspectable* pItem) +{ + return S_OK; +} + +_Check_return_ IFACEMETHODIMP IsHostForItemContainer( + xaml.IDependencyObject* pContainer, + out BOOLEAN* pIsHost) +{ + return E_NOTIMPL; +} + +_Check_return_ IFACEMETHODIMP GetGroupStyle( + xaml_data.ICollectionViewGroup* pGroup, + UINT level, + out xaml_controls.IGroupStyle** ppGroupStyle) +{ + // The modern panel is always going to ask for a GroupStyle. + // Fortunately, it's perfectly valid to return null + *ppGroupStyle = null; + return S_OK; +} + +_Check_return_ IFACEMETHODIMP SetIsGrouping( + BOOLEAN isGrouping) +{ + ASSERT(!isGrouping); + return S_OK; +} + +// we don't expose this publicly, there is an override for our own controls +// to mirror the public api +_Check_return_ IFACEMETHODIMP GetHeaderForGroup( + IInspectable* pGroup, + _Outptr_ xaml.IDependencyObject** ppContainer) +{ + return E_NOTIMPL; +} + +_Check_return_ IFACEMETHODIMP PrepareGroupContainer( + xaml.IDependencyObject* pContainer, + xaml_data.ICollectionViewGroup* pGroup) +{ + return E_NOTIMPL; +} + +_Check_return_ IFACEMETHODIMP ClearGroupContainerForGroup( + xaml.IDependencyObject* pContainer, + xaml_data.ICollectionViewGroup* pItem) +{ + return E_NOTIMPL; +} + +_Check_return_ IFACEMETHODIMP CanRecycleContainer( + xaml.IDependencyObject* pContainer, + out BOOLEAN* pCanRecycleContainer) +{ + *pCanRecycleContainer = TRUE; + return S_OK; +} + +_Check_return_ IFACEMETHODIMP SuggestContainerForContainerFromItemLookup( + _Outptr_ xaml.IDependencyObject** ppContainer) +{ + // CalendarViewGeneratorHost has no clue + *ppContainer = null; + return S_OK; +} + + +CalendarViewGeneratorHost() + : m_size(0) + , m_pOwnerNoRef(null) +{ + ResetScope(); +} + +CalendarViewGeneratorHost.~CalendarViewGeneratorHost() +{ + VERIFYHR(DetachScrollViewerFocusEngagedEvent()); + VERIFYHR(DetachVisibleIndicesUpdatedEvent()); + ctl.ComPtr panel; + if (m_tpPanel.TryGetSafeReference(&panel)) + { + VERIFYHR(panel.Cast().SetOwner(null)); + VERIFYHR(panel.Cast().SetSnapPointFilterFunction(null)); + } + + ctl.ComPtr scrollviewer; + if (m_tpScrollViewer.TryGetSafeReference(&scrollviewer)) + { + VERIFYHR(scrollviewer.Cast().SetDirectManipulationStateChangeHandler(null)); + } +} + + +wg.ICalendar* GetCalendar() +{ + return GetOwner().GetCalendar(); +} + +void ResetScope() +{ + // when scope is enabled, the current scope means the current Month for monthview, current year for yearView and current decade for decadeview + m_minDateOfCurrentScope.UniversalTime = 0; + m_maxDateOfCurrentScope.UniversalTime = 0; + m_pHeaderText.Release(); + m_lastVisibleIndicesPair[0] = -1; + m_lastVisibleIndicesPair[1] = -1; + m_lastVisitedDateAndIndex.first.UniversalTime = 0; + m_lastVisitedDateAndIndex.second = -1; + +} + +// compute how many items we have in this view, basically the number of items equals to the index of max date + 1 +_Check_return_ HRESULT ComputeSize() +{ + HRESULT hr = S_OK; + int index = 0; + + m_lastVisitedDateAndIndex.first = GetOwner().GetMinDate(); + m_lastVisitedDateAndIndex.second = 0; + + ASSERT(!GetOwner().GetDateComparer().LessThan(GetOwner().GetMaxDate(), GetOwner().GetMinDate())); + + IFC(CalculateOffsetFromMinDate(GetOwner().GetMaxDate(), &index)); + + m_size = (UINT)(index)+1; + +Cleanup: + return hr; +} + +// Add scopes to the given date. +_Check_return_ HRESULT AddScopes( wf.DateTime& date, int scopes) +{ + HRESULT hr = S_OK; + var pCalendar = GetCalendar(); + + IFC(pCalendar.SetDateTime(date)); + IFC(AddScopes(scopes)); + IFC(pCalendar.GetDateTime(&date)); + + // We coerce and check if the date is in Calendar's limit where this gets called. + +Cleanup: + return hr; +} + +_Check_return_ HRESULT AddUnits( wf.DateTime& date, int units) +{ + var pCalendar = GetCalendar(); + + IFC_RETURN(pCalendar.SetDateTime(date)); + IFC_RETURN(AddUnits(units)); + IFC_RETURN(pCalendar.GetDateTime(&date)); + + // We coerce and check if the date is in Calendar's limit where this gets called. + + return S_OK; +} + +// AddDays/AddMonths/AddYears takes O(N) time but given that at most time we +// generate the items continuously so we can cache the result from last call and +// call AddUnits from the cache - this way N is small enough +// time cost: amortized O(1) + +_Check_return_ HRESULT GetDateAt( UINT index, out wf.DateTime* pDate) +{ + wf.DateTime date = {}; + var pCalendar = GetCalendar(); + + ASSERT(m_lastVisitedDateAndIndex.second != -1); + + IFC_RETURN(pCalendar.SetDateTime(m_lastVisitedDateAndIndex.first)); + IFC_RETURN(AddUnits((int)(index) - m_lastVisitedDateAndIndex.second)); + IFC_RETURN(pCalendar.GetDateTime(&date)); + m_lastVisitedDateAndIndex.first = date; + m_lastVisitedDateAndIndex.second = (int)(index); + pDate.UniversalTime = date.UniversalTime; + + return S_OK; +} + +// to get the distance of two days, here are the amortized O(1) method +//1. Estimate the offset of Date2 from Date1 by dividing their UTC difference by 24 hours +//2. Call Globalization API AddDays(Date1, offset) to get an estimated date, let’s say EstimatedDate, here offset comes from step1 +//3. Compute the distance between EstimatedDate and Date2(keep adding 1 day on the smaller one, until we hit the another date), +// if this distance is still big, we can do step 1 and 2 one more time +//4. Return the sum of results from step1 and step3. + +_Check_return_ HRESULT CalculateOffsetFromMinDate( wf.DateTime date, out int* pIndex) +{ + *pIndex = 0; + wf.DateTime estimatedDate = { m_lastVisitedDateAndIndex.first.UniversalTime }; + var pCalendar = GetCalendar(); + ASSERT(m_lastVisitedDateAndIndex.second != -1); + + int estimatedOffset = 0; + INT64 diffInUTC = 0; + int diffInUnit = 0; + + const int maxEstimationRetryCount = 3; // the max times that we should estimate + const int maxReboundCount = 3; // the max times that we should reduce the step when the estimation is over the boundary. + const int minDistanceToEstimate = 3; // the min estimated distance that we should do estimation. + + IFC_RETURN(pCalendar.SetDateTime(estimatedDate)); + + // step 1: estimation. mostly we only need to up to 2 times, but if we are targeting the calendar's boundaries + // we could need more times (uncommon scenario) + var averageTicksPerUnit = GetAverageTicksPerUnit(); +#ifdef DBG + int estimationCount = 0; +#endif + while (true) + { + diffInUTC = date.UniversalTime - estimatedDate.UniversalTime; + + // round to the nearest integer + diffInUnit = (int)(diffInUTC / averageTicksPerUnit); + + if (std.abs(diffInUnit) < minDistanceToEstimate) + { + // if two dates are close enough, we can start to check if a correction is needed. + break; + } +#ifdef DBG + if (estimationCount++ > maxEstimationRetryCount) + { + IGNOREHR(DebugTrace(XCP_TRACE_WARNING, "CalendarViewGeneartorHost.CalculateOffsetFromMinDate[0x%p]: estimationCount = %d.", this, estimationCount)); + ASSERT(FALSE); + } +#endif + + // when we are targeting the calendar's boundaries, it is possible the estimation will + // cross the boundary, in this case we should reduce the length of step. +#ifdef DBG + int retryCount = 0; +#endif + while (true) + { + HRESULT hr = AddUnits(diffInUnit); + + if (SUCCEEDED(hr)) + break; +#ifdef DBG + if (retryCount++ > maxReboundCount) + { + IGNOREHR(DebugTrace(XCP_TRACE_WARNING, "CalendarViewGeneartorHost.CalculateOffsetFromMinDate[0x%p]: over boundary, retryCount = %d.", this, retryCount)); + ASSERT(FALSE); + } +#endif + // we crossed the boundary! reduce the length and restart from estimatedDate + // + // mostly a bad estimation could happen on two dates that have a huge difference (e.g. jump to 100 years ago), + // to fix the estimation we only need to slightly reduce the diff. + + IFC_RETURN(pCalendar.SetDateTime(estimatedDate)); + diffInUnit = diffInUnit * 99 / 100; + ASSERT(diffInUnit != 0); + } //while (true) + + estimatedOffset += diffInUnit; + + IFC_RETURN(pCalendar.GetDateTime(&estimatedDate)); + } //while (true) + + // step 2: after estimation, we'll check if a correction is needed or not. + // this will be done in O(N) time but given that we have a good enough + // estimation, here N will be very small (most likely <= 2) + int offsetCorrection = 0; + while (true) + { + int result = 0; + int step = 1; + IFC_RETURN(CompareDate(estimatedDate, date, &result)); + if (result == 0) + { + // end the loop when meeting the target date + break; + } + else if (result > 0) + { + step = -1; + } + IFC_RETURN(AddUnits(step)); + offsetCorrection += step; + IFC_RETURN(pCalendar.GetDateTime(&estimatedDate)); + } + + // base + estimatedDiff + correction + *pIndex = m_lastVisitedDateAndIndex.second + estimatedOffset + offsetCorrection; + + return S_OK; +} + +// return the first date of next scope. +// parameter dateOfFirstVisibleItem is the first visible item, it could be in +// current scope, or in previous scope. +_Check_return_ HRESULT GetFirstDateOfNextScope( + wf.DateTime dateOfFirstVisibleItem, + bool forward, + out wf.DateTime* pFirstDateOfNextScope) +{ + HRESULT hr = S_OK; + int adjustScopes = 0; + wf.DateTime firstDateOfNextScope = {}; + + // set to the first date of current scope + IFC(GetCalendar().SetDateTime(m_minDateOfCurrentScope)); + + if (!GetOwner().GetDateComparer().LessThan(m_minDateOfCurrentScope, dateOfFirstVisibleItem)) + { + // current scope starts from the first visible line + // in this case, we simply jump to previous or next scope + adjustScopes = forward ? 1 : -1; + } + else + { + // current scope starts before the first visible line, + // so when we go backwards, we go to the beginning of this scope. + // when go forwards, we still go to the next scope + adjustScopes = forward ? 1 : 0; + } + + if (adjustScopes != 0) + { + IFC(AddScopes(adjustScopes)); + + int firstUnit = 0; + GetFirstUnitInThisScope(&firstUnit); + SetUnit(firstUnit); + } + + IFC(GetCalendar().GetDateTime(&firstDateOfNextScope)); + + // when the navigation button is enabled, we should always be able to navigate to the desired scope. + ASSERT(!GetOwner().GetDateComparer().LessThan(firstDateOfNextScope, GetOwner().GetMinDate())); + ASSERT(!GetOwner().GetDateComparer().LessThan(GetOwner().GetMaxDate(), firstDateOfNextScope)); + +Cleanup: + *pFirstDateOfNextScope = firstDateOfNextScope; + return hr; +} + + +// Give a date range (it may contain multiple scopes, the scope is a month for MonthView), +// find the scope that has higher item coverage percentage, and use it as current scope. +_Check_return_ HRESULT UpdateScope( + wf.DateTime firstDate, + wf.DateTime lastDate, + out bool* isScopeChanged) +{ + HRESULT hr = S_OK; + wf.DateTime lastDateOfFirstScope; + wf.DateTime minDateOfCurrentScope; + wf.DateTime maxDateOfCurrentScope; + int firstUnit = 0; + int firstUnitOfFirstScope = 0; + int lastUnitOfFirstScope = 0; + + *isScopeChanged = false; + var pCalendar = GetCalendar(); + + ASSERT(!GetOwner().GetDateComparer().LessThan(lastDate, firstDate)); + + IFC(pCalendar.SetDateTime(firstDate)); + IFC(GetUnit(&firstUnit)); + IFC(AdjustToLastUnitInThisScope(&lastDateOfFirstScope, &lastUnitOfFirstScope)); + + if (!GetOwner().GetDateComparer().LessThan(lastDateOfFirstScope, lastDate)) + { + // The given range has only one scope, so this is the current scope + maxDateOfCurrentScope.UniversalTime = lastDateOfFirstScope.UniversalTime; + IFC(AdjustToFirstUnitInThisScope(&minDateOfCurrentScope)); + } + else + { + // The given range has more than one scopes, let's check the first one and second one. + wf.DateTime lastDateOfSecondScope; + int itemCountOfFirstScope = lastUnitOfFirstScope - firstUnit + 1; + int itemCountOfSecondScope = 0; + + wf.DateTime dateToDetermineCurrentScope; // we'll pick a date from first scope or second scope to determine the current scope. + int firstUnitOfSecondScope = 0; + int lastUnitOfSecondScope = 0; + + IFC(GetFirstUnitInThisScope(&firstUnitOfFirstScope)); + + // We are on the last unit of first scope, add 1 unit will move to the second scope + IFC(AddUnits(1)); + + IFC(GetFirstUnitInThisScope(&firstUnitOfSecondScope)); + + // Read the last date of second scope, check if it is inside the given range. + IFC(AdjustToLastUnitInThisScope(&lastDateOfSecondScope, &lastUnitOfSecondScope)); + + if (!GetOwner().GetDateComparer().LessThan(lastDate, lastDateOfSecondScope)) + { + // The given range has the whole 2nd scope + itemCountOfSecondScope = lastUnitOfSecondScope - firstUnitOfSecondScope + 1; + } + else + { + // The given range has only a part of the 2nd scope + int lastUnit = 0; + IFC(pCalendar.SetDateTime(lastDate)); + IFC(GetUnit(&lastUnit)); + itemCountOfSecondScope = lastUnit - firstUnitOfSecondScope + 1; + } + + double firstScopePercentage = (double)itemCountOfFirstScope / (lastUnitOfFirstScope-firstUnitOfFirstScope+1); + double secondScopePercentage = (double)itemCountOfSecondScope / (lastUnitOfSecondScope-firstUnitOfSecondScope+1); + + if (firstScopePercentage < secondScopePercentage) + { + // second scope wins + dateToDetermineCurrentScope.UniversalTime = lastDateOfSecondScope.UniversalTime; + } + else + { + // first scope wins + dateToDetermineCurrentScope.UniversalTime = firstDate.UniversalTime; + } + + IFC(pCalendar.SetDateTime(dateToDetermineCurrentScope)); + IFC(AdjustToFirstUnitInThisScope(&minDateOfCurrentScope)); + IFC(AdjustToLastUnitInThisScope(&maxDateOfCurrentScope)); + } + + // in case we start from a day other than first day, we need to adjust the scope. + // in case we end at a day other than the last day of this month, we need to adjust the scope. + GetOwner().CoerceDate(minDateOfCurrentScope); + GetOwner().CoerceDate(maxDateOfCurrentScope); + + if (minDateOfCurrentScope.UniversalTime != m_minDateOfCurrentScope.UniversalTime || + maxDateOfCurrentScope.UniversalTime != m_maxDateOfCurrentScope.UniversalTime) + { + m_minDateOfCurrentScope = minDateOfCurrentScope; + m_maxDateOfCurrentScope = maxDateOfCurrentScope; + *isScopeChanged = true; + + IFC(OnScopeChanged()); + } + +Cleanup: + return hr; +} + +_Check_return_ HRESULT AdjustToFirstUnitInThisScope(out wf.DateTime* pDate, _Out_opt_ int* pUnit /* = null */) +{ + int firstUnit = 0; + + if (pUnit) + { + *pUnit = 0; + } + pDate.UniversalTime = 0; + + IFC_RETURN(GetFirstUnitInThisScope(&firstUnit)); + IFC_RETURN(SetUnit(firstUnit)); + IFC_RETURN(GetCalendar().GetDateTime(pDate)); + + if (pUnit) + { + *pUnit = firstUnit; + } + + return S_OK; +} + +_Check_return_ HRESULT AdjustToLastUnitInThisScope(out wf.DateTime* pDate, _Out_opt_ int* pUnit /* = null */) +{ + int lastUnit = 0; + + if (pUnit) + { + *pUnit = 0; + } + pDate.UniversalTime = 0; + + IFC_RETURN(GetLastUnitInThisScope(&lastUnit)); + IFC_RETURN(SetUnit(lastUnit)); + IFC_RETURN(GetCalendar().GetDateTime(pDate)); + + if (pUnit) + { + *pUnit = lastUnit; + } + + return S_OK; +} + +_Check_return_ HRESULT NotifyStateChange( + DMManipulationState state, + FLOAT xCumulativeTranslation, + FLOAT yCumulativeTranslation, + FLOAT zCumulativeFactor, + FLOAT xCenter, + FLOAT yCenter, + BOOLEAN isInertial, + BOOLEAN isTouchConfigurationActivated, + BOOLEAN isBringIntoViewportConfigurationActivated) +{ + switch (state) + { + // we change items' scope state to InScope when DMManipulation is in progress to achieve better visual effect. + // note we only change when there is an actual move (e.g. Manipulation Started, not Starting), because user + // tapping to select an item also causes Manipulation starting, in this case we should not change scope state. + case DirectUI.DMManipulationStarted: + IFC_RETURN(GetOwner().UpdateItemsScopeState(this, + true, /*ignoreWhenIsOutOfScopeDisabled*/ + false /*ignoreInDirectManipulation*/)); + break; + case DirectUI.DMManipulationCompleted: + IFC_RETURN(GetOwner().UpdateItemsScopeState(this, + false, /*ignoreWhenIsOutOfScopeDisabled*/ // in case we changed IsOutOfScopeEnabled to false during DManipulation + false /*ignoreInDirectManipulation*/)); + break; + default: + break; + } + return S_OK; +} + +_Check_return_ HRESULT AttachVisibleIndicesUpdatedEvent() +{ + if (m_tpPanel) + { + IFC_RETURN(m_epVisibleIndicesUpdatedHandler.AttachEventHandler(m_tpPanel.Cast(), + [this](IInspectable* pSender, IInspectable* pArgs) + { + return GetOwner().OnVisibleIndicesUpdated(this); + })); + } + return S_OK; +} + +_Check_return_ HRESULT DetachVisibleIndicesUpdatedEvent() +{ + return DetachHandler(m_epVisibleIndicesUpdatedHandler, m_tpPanel); +} + +_Check_return_ HRESULT AttachScrollViewerFocusEngagedEvent() +{ + if (m_tpPanel) + { + ctl.ComPtr sv(m_tpScrollViewer.Cast()); + IFC_RETURN(m_epScrollViewerFocusEngagedEventHandler.AttachEventHandler(sv.AsOrNull().Get(), + [this]( xaml_controls.IControl* pSender, + xaml_controls.IFocusEngagedEventArgs* pArgs) + { + return GetOwner().OnScrollViewerFocusEngaged(pArgs); + })); + } + return S_OK; +} + +_Check_return_ HRESULT DetachScrollViewerFocusEngagedEvent() +{ + return DetachHandler(m_epScrollViewerFocusEngagedEventHandler, m_tpScrollViewer); +} + +_Check_return_ HRESULT SetPanel( xaml_primitives.ICalendarPanel* pPanel) +{ + if (pPanel) + { + SetPtrValue(m_tpPanel, pPanel); + IFC_RETURN(m_tpPanel.Cast().SetOwner(this)); + } + else if (m_tpPanel) + { + IFC_RETURN(m_tpPanel.Cast().SetOwner(null)); + m_tpPanel.Clear(); + } + return S_OK; +} + + +_Check_return_ HRESULT SetScrollViewer( xaml_controls.IScrollViewer* pScrollViewer) +{ + if (pScrollViewer) + { + SetPtrValue(m_tpScrollViewer, pScrollViewer); + } + else + { + m_tpScrollViewer.Clear(); + } + return S_OK; +} + + +CalendarPanel* GetPanel() +{ + return m_tpPanel.Cast(); +} + +ScrollViewer* GetScrollViewer() +{ + return m_tpScrollViewer.Cast(); +} + +_Check_return_ HRESULT OnPrimaryPanelDesiredSizeChanged() +{ + return GetOwner().OnPrimaryPanelDesiredSizeChanged(this); +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorHost_DataVirtualization.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorHost_DataVirtualization.cs new file mode 100644 index 000000000000..1d5de0636373 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorHost_DataVirtualization.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "precomp.h" +#include "CalendarViewGeneratorHost.h" +#include "CalendarView.g.h" +#include "CalendarViewItem.g.h" +#include "CalendarViewDayItem_Partial.h" +#include "CalendarViewDayItemChangingEventArgs.g.h" + +using namespace DirectUI; +using namespace DirectUISynonyms; + +// Work around disruptive max/min macros +#undef max +#undef min + +// DataVirtualization - to avoid creating large number of calendar items, +// we only implement GetAt and get_Size. + +// IList implementation +IFACEMETHODIMP CalendarViewGeneratorHost.GetAt( UINT index, _Outptr_ IInspectable** item) +{ + HRESULT hr = S_OK; + wf.DateTime date; + + IFC(GetDateAt(index, &date)); + + IFC(PropertyValue.CreateFromDateTime(date, item)); + +Cleanup: + return hr; +} + +IFACEMETHODIMP CalendarViewGeneratorHost.get_Size(out UINT* value) +{ + *value = m_size; + return S_OK; +} + +IFACEMETHODIMP CalendarViewGeneratorHost.GetView(_Outptr_result_maybenull_ wfc.IVectorView** view) +{ + HRESULT hr = S_OK; + ctl.ComPtr> spResult; + + IFC(CheckThread()); + ARG_VALIDRETURNPOINTER(view); + + IFC(ctl.ComObject>.CreateInstance(spResult.ReleaseAndGetAddressOf())); + spResult.Cast>().SetCollection(this); + + *view = spResult.Detach(); + +Cleanup: + + return hr; +} + +IFACEMETHODIMP CalendarViewGeneratorHost.IndexOf( IInspectable* value, out UINT* index, out BOOLEAN* found) +{ + return E_NOTIMPL; +} + +IFACEMETHODIMP CalendarViewGeneratorHost.SetAt( UINT index, IInspectable* item) +{ + return E_NOTIMPL; +} + +IFACEMETHODIMP CalendarViewGeneratorHost.InsertAt( UINT index, IInspectable* item) +{ + return E_NOTIMPL; +} + +IFACEMETHODIMP CalendarViewGeneratorHost.RemoveAt( UINT index) +{ + return E_NOTIMPL; +} + +IFACEMETHODIMP CalendarViewGeneratorHost.Append( IInspectable* item) +{ + return E_NOTIMPL; +} + +IFACEMETHODIMP CalendarViewGeneratorHost.RemoveAtEnd() +{ + return E_NOTIMPL; +} + +IFACEMETHODIMP CalendarViewGeneratorHost.Clear() +{ + return E_NOTIMPL; +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorMonthViewHost.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorMonthViewHost.cs new file mode 100644 index 000000000000..38b43423f79a --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorMonthViewHost.cs @@ -0,0 +1,323 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "precomp.h" +#include "CalendarViewGeneratorMonthViewHost.h" +#include "CalendarView.g.h" +#include "CalendarViewItem.g.h" +#include "CalendarViewDayItem_Partial.h" +#include "CalendarViewDayItemChangingEventArgs.g.h" +#include "DateComparer.h" +#include "BuildTreeService.g.h" +#include "BudgetManager.g.h" + +using namespace DirectUI; +using namespace DirectUISynonyms; + +// Work around disruptive max/min macros +#undef max +#undef min + + +CalendarViewGeneratorMonthViewHost() + : m_lowestPhaseInQueue(-1) + , m_isRegisteredForCallbacks() + , m_budget(BUDGET_MANAGER_DEFAULT_LIMIT) +{ +} + +_Check_return_ HRESULT GetContainer( + IInspectable* pItem, + xaml.IDependencyObject* pRecycledContainer, + _Outptr_ CalendarViewBaseItem** ppContainer) +{ + HRESULT hr = S_OK; + ctl.ComPtr spContainer; + + IFC(ctl.new CalendarViewDayItem(&spContainer)); + + IFC(spContainer.CopyTo(ppContainer)); + +Cleanup: + return hr; +} + +_Check_return_ IFACEMETHODIMP PrepareItemContainer( + xaml.IDependencyObject* pContainer, + IInspectable* pItem) +{ + HRESULT hr = S_OK; + wf.DateTime date; + ctl.ComPtr spContainer; + + spContainer = (CalendarViewDayItem*)(pContainer); + + // first let's check if this container is already in the tobecleared queue + // the container might be already marked as to be cleared but not cleared yet, + // if we pick up this container we don't need to clear it up. + IFC(RemoveToBeClearedContainer(spContainer.Get())); + + // now prepare the container + + IFC(ctl.do_get_value(date, pItem)); + IFC(GetCalendar().SetDateTime(date)); + + IFC(spContainer.put_Date(date)); + + // main text + { + wrl_wrappers.HString mainText; + + IFC(GetCalendar().DayAsString(mainText.GetAddressOf())); + IFC(spContainer.UpdateMainText(mainText.Get())); + } + + // label text + { + BOOLEAN isLabelVisible = FALSE; + + IFC(GetOwner().get_IsGroupLabelVisible(&isLabelVisible)); + + IFC(UpdateLabel(spContainer.Get(), !!isLabelVisible)); + } + + // today state will be updated in CalendarViewGeneratorHost.PrepareItemContainer + + // clear the blackout state and set correct selection state. + { + // we don't have a copy of blackout items (that could be too many), instead developer needs to tell us + // about the blackout and densitybar properties when in CIC event. As both properties are on the container (not like selected dates) so when + // the container is being reused, we'll need to clear these properties and developer may set them again if they want. + // there is a discussion that who should clear the flags - the developer clear them in phase 0 (in CIC phase 0 event) + // or we clear them in phase 0 (before CIC phase 0 event). the result is we do this to make the logical simple. + + // reset the blackout state + IFC(spContainer.put_IsBlackout(FALSE)); + + // set selection state. + bool isSelected = false; + IFC(GetOwner().IsSelected(date, &isSelected)); + IFC(spContainer.SetIsSelected(isSelected)); + } + + // clear the density bar as well. + IFC(spContainer.SetDensityColors(null)); + + // apply style to CalendarViewDayItem if any. + { + ctl.ComPtr spStyle; + + IFC(GetOwner().get_CalendarViewDayItemStyle(&spStyle)); + CalendarView.SetDayItemStyle(spContainer.Get(), spStyle.Get()); + } + + IFC(CalendarViewGeneratorHost.PrepareItemContainer(pContainer, pItem)); + +Cleanup: + return hr; +} + +_Check_return_ HRESULT UpdateLabel( CalendarViewBaseItem* pItem, bool isLabelVisible) +{ + bool showLabel = false; + if (isLabelVisible) + { + wf.DateTime date; + var pCalendar = GetCalendar(); + int day = 0; + int firstDayInThisMonth = 0; + + // TODO: consider caching the firstday flag because we also need this information when determining snap points + // (however Decadeview doesn't need this for Label). + IFC_RETURN(pItem.GetDate(&date)); + IFC_RETURN(pCalendar.SetDateTime(date)); + IFC_RETURN(pCalendar.get_FirstDayInThisMonth(&firstDayInThisMonth)); + IFC_RETURN(pCalendar.get_Day(&day)); + showLabel = firstDayInThisMonth == day; + + if (showLabel) + { + wrl_wrappers.HString labelText; + IFC_RETURN(pCalendar.MonthAsString( + 0, /*idealLength, set to 0 to get the abbreviated string*/ + labelText.GetAddressOf())); + IFC_RETURN(pItem.UpdateLabelText(labelText.Get())); + } + } + IFC_RETURN(pItem.ShowLabelText(showLabel)); + return S_OK; +} + +// reset CIC event if the container is being cleared. +_Check_return_ IFACEMETHODIMP ClearContainerForItem( + xaml.IDependencyObject* pContainer, + IInspectable* pItem) +{ + HRESULT hr = S_OK; + + ctl.ComPtr spContainer = (CalendarViewDayItem*)(pContainer); + + // There is much perf involved with doing a clear, and usually it is going to be + // a waste of time since we are going to immediately follow up with a prepare. + // Perf traces have found this to be about 8 to 12% during a full virtualization pass (!!) + // Although with other optimizations we would expect that to go down, it is unlikely to go + // down to 0. Therefore we are deferring the impl work here to later. + // We have decided to do this only for the new panels. + + // also, do not defer items that are uielement. They need to be cleared straight away so that + // they can be messed with again. + IFC(m_toBeClearedContainers.Append(spContainer.Get())); + + // note that if we are being cleared, we are not going to be in the + // visible index, or the caches. And thus we will never be called in the + // prepare queuing part. + + if (!m_isRegisteredForCallbacks) + { + ctl.ComPtr spBuildTree; + IFC(DXamlCore.GetCurrent().GetBuildTreeService(spBuildTree)); + IFC(spBuildTree.RegisterWork(this)); + } + ASSERT(m_isRegisteredForCallbacks); + + +Cleanup: + return hr; +} + +_Check_return_ HRESULT GetIsFirstItemInScope( int index, out bool* pIsFirstItemInScope) +{ + HRESULT hr = S_OK; + + *pIsFirstItemInScope = false; + if (index == 0) + { + *pIsFirstItemInScope = true; + } + else + { + wf.DateTime date = {}; + int day = 0; + int firstDay = 0; + + IFC(GetDateAt(index, &date)); + var pCalendar = GetCalendar(); + IFC(pCalendar.SetDateTime(date)); + IFC(pCalendar.get_Day(&day)); + IFC(pCalendar.get_FirstDayInThisMonth(&firstDay)); + *pIsFirstItemInScope = day == firstDay; + } + +Cleanup: + return hr; +} + +_Check_return_ HRESULT GetUnit(out int* pValue) +{ + return GetCalendar().get_Day(pValue); +} + +_Check_return_ HRESULT SetUnit( int value) +{ + return GetCalendar().put_Day(value); +} + +_Check_return_ HRESULT AddUnits( int value) +{ + return GetCalendar().AddDays(value); +} + +_Check_return_ HRESULT AddScopes( int value) +{ + return GetCalendar().AddMonths(value); +} + +_Check_return_ HRESULT GetFirstUnitInThisScope(out int* pValue) +{ + return GetCalendar().get_FirstDayInThisMonth(pValue); +} +_Check_return_ HRESULT GetLastUnitInThisScope(out int* pValue) +{ + return GetCalendar().get_LastDayInThisMonth(pValue); +} + +_Check_return_ HRESULT OnScopeChanged() +{ + return GetOwner().FormatMonthYearName(m_minDateOfCurrentScope, m_pHeaderText.ReleaseAndGetAddressOf()); +} + +_Check_return_ HRESULT GetPossibleItemStrings(_Outptr_ const std.vector** ppStrings) +{ + *ppStrings = &m_possibleItemStrings; + + if (m_possibleItemStrings.empty()) + { + // for all known calendar identifiers so far (10 different calendar identifiers), we can find the longest month in no more than 3 months + // if we start from min date of this calendar. + + // below are the longest month and the lowest index of that month we found for each calendar identifier. + // we hope that any new calendar in the future don't break this rule. + + // PersianCalendar, maxLength = 31 @ index 0 + // GregorianCalendar, maxLength = 31 @ index 0 + // HebrewCalendar, maxLength = 30 @ index 1 + // HijriCalendar, maxLength = 30 @ index 0 + // JapaneseCalendar, maxLength = 31 @ index 0 + // JulianCalendar, maxLength = 31 @ index 2 + // KoreanCalendar, maxLength = 31 @ index 0 + // TaiwanCalendar, maxLength = 31 @ index 0 + // ThaiCalendar, maxLength = 31 @ index 0 + // UmAlQuraCalendar, maxLength = 30 @ index 1 + + { + const int MaxNumberOfMonthsToBeChecked = 3; + wf.DateTime longestMonth; + int lengthOfLongestMonth = 0; + int numberOfDays = 0; + int day = 0; + + var pCalendar = GetCalendar(); + + IFC_RETURN(pCalendar.SetToMin()); + for (int i = 0; i < MaxNumberOfMonthsToBeChecked; i++) + { + IFC_RETURN(pCalendar.get_NumberOfDaysInThisMonth(&numberOfDays)); + if (numberOfDays > lengthOfLongestMonth) + { + lengthOfLongestMonth = numberOfDays; + IFC_RETURN(pCalendar.GetDateTime(&longestMonth)); + } + IFC_RETURN(pCalendar.AddMonths(1)); + } + + ASSERT(lengthOfLongestMonth == 30 || lengthOfLongestMonth == 31); + IFC_RETURN(pCalendar.SetDateTime(longestMonth)); + IFC_RETURN(pCalendar.get_FirstDayInThisMonth(&day)); + IFC_RETURN(pCalendar.put_Day(day)); + + m_possibleItemStrings.reserve(lengthOfLongestMonth); + + for (int i = 0; i < lengthOfLongestMonth; i++) + { + wrl_wrappers.HString string; + IFC_RETURN(pCalendar.DayAsString(string.GetAddressOf())); + m_possibleItemStrings.emplace_back(std.move(string)); + IFC_RETURN(pCalendar.AddDays(1)); + } + } + } + + return S_OK; +} + +_Check_return_ HRESULT CompareDate( wf.DateTime lhs, wf.DateTime rhs, out int* pResult) +{ + return GetOwner().GetDateComparer().CompareDay(lhs, rhs, pResult); +} + +INT64 GetAverageTicksPerUnit() +{ + // this is being used to estimate the distance between two dates, + // it doesn't need to be (and it can't be) the exact value + return CalendarConstants.s_ticksPerDay; +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorMonthViewHost_ContainerPhase.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorMonthViewHost_ContainerPhase.cs new file mode 100644 index 000000000000..31cc9968ce47 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorMonthViewHost_ContainerPhase.cs @@ -0,0 +1,753 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "precomp.h" +#include "CalendarViewGeneratorMonthViewHost.h" +#include "CalendarView.g.h" +#include "CalendarViewItem.g.h" +#include "CalendarViewDayItem_Partial.h" +#include "CalendarViewDayItemChangingEventArgs.g.h" +#include "CalendarPanel.g.h" +#include "BuildTreeService.g.h" +#include "BudgetManager.g.h" + +using namespace DirectUI; +using namespace DirectUISynonyms; + +// Work around disruptive max/min macros +#undef max +#undef min + +// most codes are copied from ListViewBase_Partial_containerPhase.cpp +// ListViewBase CCC event is restricted with ListViewBase, and it considers about UIPlaceHolder +// the CalendarView version removes UIPlaceHolder and handles blackout state. +// Other logicals are still same as ListViewBase. + +_Check_return_ HRESULT CalendarViewGeneratorMonthViewHost.SetupContainerContentChangingAfterPrepare( + xaml.IDependencyObject* pContainer, + IInspectable* pItem, + INT itemIndex, + wf.Size measureSize) +{ + HRESULT hr = S_OK; + + // this is being called by modern panels after the prepare has occurred. + // we will setup information that we will need during the lifetime of this container + + ctl.ComPtr spContainer; + ctl.ComPtr spArgsConcrete; + + // raw pointer, since we're not a refcounted object + UIElement.VirtualizationInformation* pVirtualizationInformation = null; + + spContainer = ctl.query_interface_cast(pContainer); + + if (spContainer) + { + // is this a new style container? We can know by looking at the virtualizationInformation struct which is + // a ModernCollectionBase concept + ctl.ComPtr spArgs; + + pVirtualizationInformation = spContainer.AsOrNull().Cast().GetVirtualizationInformation(); + + ASSERT(pVirtualizationInformation); + + IFC(spContainer.Cast().GetBuildTreeArgs(&spArgs)); + spArgsConcrete = spArgs.Cast(); + } + + // store the size we would measure with + pVirtualizationInformation.SetMeasureSize(measureSize); + + // initialize values in the args + IFC(spArgsConcrete.put_WantsCallBack(FALSE)); // let them explicitly call-out if they want it + + IFC(spArgsConcrete.put_Item(spContainer.Get())); // there is now a hard ref + IFC(spArgsConcrete.put_InRecycleQueue(FALSE)); + IFC(spArgsConcrete.put_Phase(0)); + + // raise the event. This is the synchronous version. we will raise it 'async' as well when we have time + // but we guarantee calling it after prepare + if (GetOwner().ShouldRaiseEvent(KnownEventIndex.CalendarView_CalendarViewDayItemChanging)) // app code hooks the event + { + CalendarView.CalendarViewDayItemChangingEventSourceType* pEventSource = null; + + IFC(GetOwner().GetCalendarViewDayItemChangingEventSourceNoRef(&pEventSource)); + + // force measure. This will be no-op since content has not been set/changed + // but we need it for the contenttemplateroot + IFC(spContainer.Cast().Measure(measureSize)); + + IFC(pEventSource.Raise(GetOwner(), spArgsConcrete.Get())); + } + + IFC(RegisterWorkFromCICArgs(spArgsConcrete.Get())); + +Cleanup: + return hr; +} + +_Check_return_ HRESULT CalendarViewGeneratorMonthViewHost.RegisterWorkForContainer( + xaml.IUIElement* pContainer) +{ + HRESULT hr = S_OK; + ctl.ComPtr spArgs; + ctl.ComPtr spContainer; + + IFC(ctl.do_query_interface(spContainer, pContainer)); + IFC(spContainer.Cast().GetBuildTreeArgs(&spArgs)); + + IFC(RegisterWorkFromCICArgs(spArgs.Get())); + +Cleanup: + return hr; +} + +_Check_return_ HRESULT +CalendarViewGeneratorMonthViewHost.RemoveToBeClearedContainer( CalendarViewDayItem* pContainer) +{ + HRESULT hr = S_OK; + + // we might have been inserted into the list for deferred clear container calls. + // the fact that we are now being prepared, means that we don't have to perform that clear call. + // yay! that means we are going to not perform work that has quite a bit of perf overhead. + // --- + // we happen to know that the clear will have been pushed to the back of the vector, so optimize + // the panning scenario by checking in reverse order + + // special case the last element (since we push_back during when we called clear and we expect the next + // action to be this prepare). + UINT toBeClearedContainerCount = 0; + IFC(EnsureToBeClearedContainers()); + IFC(m_toBeClearedContainers.get_Size(&toBeClearedContainerCount)); + + for (UINT current = toBeClearedContainerCount - 1; current >= 0 && toBeClearedContainerCount > 0; --current) + { + ctl.ComPtr spCurrentContainer; + // go from back to front, since we're most likely in the back. + IFC(m_toBeClearedContainers.GetAt(current, &spCurrentContainer)); + + if (spCurrentContainer.Get() == (ICalendarViewDayItem*)(pContainer)) + { + IFC(m_toBeClearedContainers.RemoveAt(current)); + break; + } + + if (current == 0) + { + // UINT + break; + } + } +Cleanup: + return hr; +} + +IFACEMETHODIMP CalendarViewGeneratorMonthViewHost.get_IsRegisteredForCallbacks(out BOOLEAN* pValue) +{ + *pValue = m_isRegisteredForCallbacks; + return S_OK; +} + +IFACEMETHODIMP CalendarViewGeneratorMonthViewHost.put_IsRegisteredForCallbacks( BOOLEAN value) +{ + m_isRegisteredForCallbacks = value; + return S_OK; +} + +_Check_return_ HRESULT CalendarViewGeneratorMonthViewHost.IsBuildTreeSuspended(out BOOLEAN* pReturnValue) +{ + var pOwner = GetOwner().GetHandle(); + *pReturnValue = pOwner.IsCollapsed() || !pOwner.AreAllAncestorsVisible(); + return S_OK; +} + +// the async version of doWork that is being called by NWDrawTree +IFACEMETHODIMP CalendarViewGeneratorMonthViewHost.BuildTree(out BOOLEAN* pWorkLeft) +{ + HRESULT hr = S_OK; + INT timeElapsedInMS = 0; + ctl.ComPtr spBudget; + + *pWorkLeft = TRUE; + + IFC(DXamlCore.GetCurrent().GetBudgetManager(spBudget)); + IFC(spBudget.GetElapsedMilliSecondsSinceLastUITick(&timeElapsedInMS)); + + if ((UINT)(timeElapsedInMS) <= m_budget) + { + var pCalendarPanel = GetPanel(); + + if (pCalendarPanel) + { + // we are going to do different types of work: + // 1. process incremental visualization + // 2. process deferred clear container work + // 3. process extra cache + + // at this point, cache indices are set correctly + // We might be going several passes over the containers. Currently we are not keeping those containers + // in a particular datastructure, but we are re-using the children collection on our moderncollectionbasepanel + // We also have a nice hint (m_lowestPhaseInQueue) that tells us what phase to look out for. While we are doing our + // walk, we're going to build up a structure that allows us to do the second walks much faster. + // When we are done, we'll throw it away. + + // we do not want to do incremental loading when we are not in the live tree. + // 1. process incremental visualization + if (GetOwner().IsInLiveTree() && pCalendarPanel.IsInLiveTree()) + { + IFC(ProcessIncrementalVisualization(spBudget, pCalendarPanel)); + } + + // 2. Clear containers + // BUG#1331271 - make sure containers can be cleared even if CalendarView is not in live tree + IFC(ClearContainers(spBudget)); + + UINT containersToClearCount = 0; + IFC(m_toBeClearedContainers.get_Size(&containersToClearCount)); + + // we have work left if we still have containers that need to finish their phases + // or when we have containers that need to be cleared + *pWorkLeft = m_lowestPhaseInQueue != -1 || containersToClearCount > 0; + } + } + +Cleanup: + return hr; +} + +_Check_return_ IFACEMETHODIMP CalendarViewGeneratorMonthViewHost.RaiseContainerContentChangingOnRecycle( + xaml.IUIElement* pContainer, + IInspectable* pItem) +{ + HRESULT hr = S_OK; + + ctl.ComPtr spArgs; + ctl.ComPtr spArgsConcrete; + ctl.ComPtr spContainer; + + IFC(ctl.do_query_interface(spContainer, pContainer)); + IFC(spContainer.Cast().GetBuildTreeArgs(&spArgs)); + spArgsConcrete = spArgs.Cast(); + + if (GetOwner().ShouldRaiseEvent(KnownEventIndex.CalendarView_CalendarViewDayItemChanging)) + { + BOOLEAN wantCallback = FALSE; + CalendarView.CalendarViewDayItemChangingEventSourceType* pEventSource = null; + + IFC(GetOwner().GetCalendarViewDayItemChangingEventSourceNoRef(&pEventSource)); + + IFC(spArgsConcrete.put_InRecycleQueue(TRUE)); + IFC(spArgsConcrete.put_Phase(0)); + IFC(spArgsConcrete.put_WantsCallBack(FALSE)); + IFC(spArgsConcrete.put_Callback(null)); + IFC(spArgsConcrete.put_Item(spContainer.Get())); + + IFC(pEventSource.Raise(GetOwner(), spArgs.Get())); + IFC(spArgs.Cast().get_WantsCallBack(&wantCallback)); + + if (wantCallback) + { + if (m_lowestPhaseInQueue == -1) + { + UINT phaseArgs = 0; + IFC(spArgs.Cast().get_Phase(&phaseArgs)); + + // there was nothing registered + m_lowestPhaseInQueue = phaseArgs; + + // that means we need to register ourselves with the buildtreeservice so that + // we can get called back to do some work + if (!m_isRegisteredForCallbacks) + { + ctl.ComPtr spBuildTree; + IFC(DXamlCore.GetCurrent().GetBuildTreeService(spBuildTree)); + IFC(spBuildTree.RegisterWork(this)); + } + } + } + else + { + IFC(spArgsConcrete.ResetLifetime()); + } + } + else + { + IFC(spArgsConcrete.ResetLifetime()); + } + +Cleanup: + return hr; +} + +_Check_return_ HRESULT +CalendarViewGeneratorMonthViewHost.ProcessIncrementalVisualization( + const ctl.ComPtr& spBudget, + CalendarPanel* pCalendarPanel) +{ + HRESULT hr = S_OK; + + if (m_lowestPhaseInQueue > -1) + { + INT timeElapsedInMS = 0; + ctl.ComPtr spMapping; + // A block structure has been considered, but we do expect to continuously mutate the phase on containers, which would have + // cost us perf while reflecting that in the blocks. Instead, I keep an array of the size of the amount of containers i'm interested in. + // The idea is that walking through that multiple times is still pretty darn fast. + + xaml_controls.PanelScrollingDirection direction = xaml_controls.PanelScrollingDirection.PanelScrollingDirection_None; + + // the following four indices will be fetched from the panel + // notice how they are not guaranteed not to be stale: one scenario in which they are + // plain and simply wrong is when you collapse/remove a panel from the tree and start + // mutating the collection. Arrange will not get a chance to run after the mutation and + // if we are still registered to do work, we will not be able to fetch the container. + INT cacheStart = -1; + INT visibleStart = -1; + INT visibleEnd = -1; + INT cacheEnd = -1; + + IFC(pCalendarPanel.GetItemContainerMapping(&spMapping)); + IFC(pCalendarPanel.get_FirstCacheIndexBase(&cacheStart)); + IFC(pCalendarPanel.get_FirstVisibleIndexBase(&visibleStart)); + IFC(pCalendarPanel.get_LastVisibleIndexBase(&visibleEnd)); + IFC(pCalendarPanel.get_LastCacheIndexBase(&cacheEnd)); + + // these four match the indices, except they are mapped into a lookup array. + // notice however, that we understand how visibleindex could have been -1. + INT cacheStartInVector = -1; + INT visibleStartInVector = -1; + INT visibleEndInVector = -1; + INT cacheEndInVector = -1; + + // translate to array indices + if (cacheEnd > -1) // -1 means there is no thing.. no visible or cached containers + { + cacheStartInVector = 0; + visibleStartInVector = visibleStart > -1 ? visibleStart - cacheStart : 0; + visibleEndInVector = visibleEnd > -1 ? visibleEnd - cacheStart : visibleStartInVector; + cacheEndInVector = cacheEnd - cacheStart; + + } + else + { + // well, nothing to do, + m_lowestPhaseInQueue = -1; + } + + // start off uninitialized + INT currentPositionInVector = -1; + + IFC(pCalendarPanel.get_PanningDirectionBase(&direction)); + + // trying to find containers in this phase + INT64 processingPhase = m_lowestPhaseInQueue; + // when we are done with a full iteration, will be looking for the nextlowest phase + // note: INT64 here so we can set it to a max that is out of range of the public phase property which is INT + // max is used to indicate there is nothing left to work through. + INT64 nextLowest = std.numeric_limits.max(); + + // policy is to go through visible children first, then buffer in panning direction, then buffer in non-panning direction + // this method will help us do that + var ProcessCurrentPosition = [&]() + { + BOOLEAN increasePhase = FALSE; + BOOLEAN forward = direction != xaml_controls.PanelScrollingDirection.PanelScrollingDirection_Backward; + + // initialize + if (currentPositionInVector == -1) + { + currentPositionInVector = forward ? visibleStartInVector : visibleEndInVector; + } + else + { + if (forward) + { + // go forward until you reach the end of the forward buffer, and start in the opposite buffer + // going in the opposite direction + if (currentPositionInVector >= visibleStartInVector) + { + ++currentPositionInVector; + if (currentPositionInVector > cacheEndInVector) + { + currentPositionInVector = visibleStartInVector - 1; // set to the start of the left section + } + } + else + { + // processing to the left + --currentPositionInVector; + } + + // run off or no cache on left side + if (currentPositionInVector < 0) + { + currentPositionInVector = visibleStartInVector; + increasePhase = TRUE; + } + } + else + { + if (currentPositionInVector <= visibleEndInVector) + { + --currentPositionInVector; + if (currentPositionInVector < 0) + { + currentPositionInVector = visibleEndInVector + 1; // set to the start of the right section + } + } + else + { + // processing to the right + ++currentPositionInVector; + } + + // run off or no cache on right side + if (currentPositionInVector > cacheEndInVector) + { + currentPositionInVector = visibleEndInVector; + increasePhase = TRUE; + } + } + + if (increasePhase) + { + processingPhase = nextLowest; + // when increasing, signal using max() that we can stop after this iteration. + // this will be undone by the loop, resetting it to the actual value + nextLowest = std.numeric_limits.max(); + } + } + }; + + // the array that we keep iterating until we're done + // -1 means, not fetched yet + std.vector lookup; + lookup.assign(cacheEndInVector + 1, -1); + // initialize before going into the loop + ProcessCurrentPosition(); + + while (processingPhase != std.numeric_limits.max() + && (UINT)(timeElapsedInMS) < m_budget + && !lookup.empty()) + { + INT64 phase = 0; + ctl.ComPtr spContainer; + ctl.ComPtr spContainerAsCalendarViewDayItem; + UIElement.VirtualizationInformation* pVirtualizationInformation = null; + ctl.ComPtr spArgs; + ctl.ComPtr spArgsConcrete; + // always update the current position when done, except when the phase requested is lower than current phase + bool shouldUpdateCurrentPosition = true; + + // what is the phase? + phase = lookup[currentPositionInVector]; + if (phase == -1) + { + // not initialized yet + UINT argsPhase = 0; + + IFC(spMapping.ContainerFromIndex(cacheStart + currentPositionInVector, &spContainer)); + if (!spContainer) + { + // this is possible when mutations have occurred to the collection but we + // cannot be reached through layout. This is very hard to figure out, so we just harden + // the code here to deal with nulls. + ProcessCurrentPosition(); + continue; + } + + IFC(spContainer.As(&spContainerAsCalendarViewDayItem)); + + pVirtualizationInformation = spContainer.AsOrNull().Cast().GetVirtualizationInformation(); + IFC(spContainerAsCalendarViewDayItem.Cast().GetBuildTreeArgs(&spArgs)); + + IFC(spArgs.get_Phase(&argsPhase)); + phase = argsPhase; // fits easily + + lookup[currentPositionInVector] = phase; + } + + if (!spArgs) + { + // we might have skipped getting the args, let's do that now. + IFC(spMapping.ContainerFromIndex(cacheStart + currentPositionInVector, &spContainer)); + + IFC(spContainer.As(&spContainerAsCalendarViewDayItem)); + pVirtualizationInformation = spContainer.AsOrNull().Cast().GetVirtualizationInformation(); + + IFC(spContainerAsCalendarViewDayItem.Cast().GetBuildTreeArgs(&spArgs)); + } + + // guaranteed to have spArgs now + spArgsConcrete = spArgs.Cast(); + + if (phase == processingPhase) + { + // processing this guy + BOOLEAN wantsCallBack = FALSE; + wf.Size measureSize = {}; + + ctl.ComPtr> spCallback; + + // guaranteed to have pVirtualizationInformation by now + + ASSERT(pVirtualizationInformation); + + measureSize = pVirtualizationInformation.GetMeasureSize(); + + // did we store a callback + IFC(spArgsConcrete.get_Callback(&spCallback)); + + // raise event + if (spCallback.Get()) + { + IFC(spArgsConcrete.put_WantsCallBack(FALSE)); + // clear out the delegate + IFC(spArgsConcrete.put_Callback(null)); + + IFC(spCallback.Invoke(GetOwner(), spArgs.Get())); + + // the invoke will cause them to call RegisterCallback which will overwrite the delegate (fine) + // and set the boolean below to true + IFC(spArgsConcrete.get_WantsCallBack(&wantsCallBack)); + } + + // the user might have changed elements. In order to keep the budget fair, we need to try and incur + // most of the cost right now. + IFC(spContainerAsCalendarViewDayItem.Cast().Measure(measureSize)); + + // register callback + if (wantsCallBack) + { + UINT phaseFromArgs = 0; + IFC(spArgsConcrete.get_Phase(&phaseFromArgs)); + phase = phaseFromArgs; + lookup[currentPositionInVector] = phase; + + // if the appcode requested a phase that is lower than the current processing phase, it is kind of weird + if (phase < processingPhase) + { + // after we change the processingphase, our next lowest is going to be the current phase (we didn't finish it yet) + nextLowest = processingPhase; + + // change our processing phase to the requested phase. It is going to be the one we work on next + processingPhase = phase; + m_lowestPhaseInQueue = processingPhase; + + // the pointer is pointing to the current container which is great + shouldUpdateCurrentPosition = false; + } + else + { + // update the next lowest to the best of our current understanding + nextLowest = Math.Min(nextLowest, (INT64)(phase)); + } + } + else + { + // won't be called again for the lifetime of this container + IFC(spArgsConcrete.ResetLifetime()); + + // we do not have to update the next lowest. We are still processing this phase and will + // continue to do so (procesingPhase is still valid). + } + } + else //if (phase == processingPhase) + { + // if we hit a container that is registered for a callback (so he wants to iterate over phases) + // but is currently at a different phase, we need to make sure that the next lowest is set. + BOOLEAN wantsCallBack = FALSE; + IFC(spArgsConcrete.get_WantsCallBack(&wantsCallBack)); + + if (wantsCallBack) + { + ASSERT(phase > processingPhase); + // update the next lowest, now that we have seen a phase that is higher than our current processing phase + nextLowest = Math.Min(nextLowest, (INT64)(phase)); + } + } + + // updates the current position in the correct direction + if (shouldUpdateCurrentPosition) + { + ProcessCurrentPosition(); + } + // updates the time + IFC(spBudget.GetElapsedMilliSecondsSinceLastUITick(&timeElapsedInMS)); + } + + if (processingPhase == std.numeric_limits.max()) + { + // nothing left to process + m_lowestPhaseInQueue = -1; + } + else + { + // we broke out of the loop for some other reason (policy) + // should be safe at this point + ASSERT(processingPhase < std.numeric_limits.max()); + m_lowestPhaseInQueue = (INT)(processingPhase); + } + } + +Cleanup: + return hr; +} + +_Check_return_ HRESULT CalendarViewGeneratorMonthViewHost.EnsureToBeClearedContainers() +{ + HRESULT hr = S_OK; + + if (!m_toBeClearedContainers) + { + ctl.ComPtr> spContainersForClear; + + IFC(ctl.make(&spContainersForClear)); + SetPtrValue(m_toBeClearedContainers, std.move(spContainersForClear)); + } + +Cleanup: + return hr; +} + +_Check_return_ HRESULT CalendarViewGeneratorMonthViewHost.ClearContainers( + const ctl.ComPtr& spBudget) +{ + HRESULT hr = S_OK; + UINT containersToClearCount = 0; + INT timeElapsedInMS = 0; + + IFC(EnsureToBeClearedContainers()); + + IFC(m_toBeClearedContainers.get_Size(&containersToClearCount)); + for (UINT toClearIndex = containersToClearCount - 1; toClearIndex >= 0 && containersToClearCount > 0; --toClearIndex) + { + ctl.ComPtr spContainer; + + IFC(spBudget.GetElapsedMilliSecondsSinceLastUITick(&timeElapsedInMS)); + + if ((UINT)(timeElapsedInMS) > m_budget) + { + break; + } + + IFC(m_toBeClearedContainers.GetAt(toClearIndex, &spContainer)); + IFC(m_toBeClearedContainers.RemoveAtEnd()); + + // execute the deferred work + // apparently we were not going to reuse this container immediately again, so + // let's do the work now + + // we don't need the spItem because 1. we didn't save this information, 2. CalendarViewGeneratorHost.ClearContainerForItem is no-op for now + // if we need this we could simple restore the spItem from the container. + IFC(CalendarViewGeneratorHost.ClearContainerForItem(spContainer.Cast(), null /*spItem*/)); + + // potentially raise the event + if (spContainer) + { + IFC(RaiseContainerContentChangingOnRecycle(spContainer.AsOrNull().Get(), null)); + } + + if (toClearIndex == 0) + { + // UINT + break; + } + } + +Cleanup: + return hr; +} + +IFACEMETHODIMP CalendarViewGeneratorMonthViewHost.ShutDownDeferredWork() +{ + HRESULT hr = S_OK; + + ctl.ComPtr spMapping; + + var pCalendarPanel = GetPanel(); + + if (pCalendarPanel) + { + // go through everyone that might have work registered for a prepare + INT cacheStart, cacheEnd = 0; + IFC(pCalendarPanel.get_FirstCacheIndexBase(&cacheStart)); + IFC(pCalendarPanel.get_LastCacheIndexBase(&cacheEnd)); + IFC(pCalendarPanel.GetItemContainerMapping(&spMapping)); + + for (INT i = cacheStart; i < cacheEnd; ++i) + { + ctl.ComPtr spContainer; + ctl.ComPtr spArgs; + IFC(spMapping.ContainerFromIndex(i, &spContainer)); + + if (!spContainer) + { + // apparently a sentinel. This should not occur, however, during shutdown we could + // run into this since measure might not have been processed yet + continue; + } + + IFC(spContainer.Cast().GetBuildTreeArgs(&spArgs)); + + IFC(spArgs.Cast().ResetLifetime()); + } + } + +Cleanup: + return hr; +} + +_Check_return_ HRESULT CalendarViewGeneratorMonthViewHost.RegisterWorkFromCICArgs( + xaml_controls.ICalendarViewDayItemChangingEventArgs* pArgs) +{ + HRESULT hr = S_OK; + + BOOLEAN wantsCallback = FALSE; + ctl.ComPtr spCalendarViewDayItem; + CalendarViewDayItemChangingEventArgs* pConcreteArgsNoRef = (CalendarViewDayItemChangingEventArgs*)(pArgs); + + IFC(pConcreteArgsNoRef.get_WantsCallBack(&wantsCallback)); + IFC(pConcreteArgsNoRef.get_Item(&spCalendarViewDayItem)); + + // we are going to want to be called back if: + // 1. we are still showing the placeholder + // 2. app code registered to be called back + if (wantsCallback) + { + UINT phase = 0; + + IFC(pConcreteArgsNoRef.get_Phase(&phase)); + + // keep this state on the listviewbase + if (m_lowestPhaseInQueue == -1) + { + // there was nothing registered + m_lowestPhaseInQueue = phase; + + // that means we need to register ourselves with the buildtreeservice so that + // we can get called back to do some work + if (!m_isRegisteredForCallbacks) + { + ctl.ComPtr spBuildTree; + IFC(DXamlCore.GetCurrent().GetBuildTreeService(spBuildTree)); + IFC(spBuildTree.RegisterWork(this)); + } + + ASSERT(m_isRegisteredForCallbacks); + } + else if (m_lowestPhaseInQueue > phase) + { + m_lowestPhaseInQueue = phase; + } + } + else + { + // well, app code doesn't want a callback so cleanup the args + IFC(pConcreteArgsNoRef.ResetLifetime()); + } + +Cleanup: + return hr; +} diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorYearViewHost.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorYearViewHost.cs new file mode 100644 index 000000000000..520446e175db --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewGeneratorYearViewHost.cs @@ -0,0 +1,269 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "precomp.h" +#include "CalendarViewGeneratorYearViewHost.h" +#include "CalendarView.g.h" +#include "CalendarViewItem.g.h" +#include "DateComparer.h" +#include "AutomationProperties.h" + +using namespace DirectUI; +using namespace DirectUISynonyms; + +// Work around disruptive max/min macros +#undef max +#undef min + +_Check_return_ HRESULT GetContainer( + IInspectable* pItem, + xaml.IDependencyObject* pRecycledContainer, + _Outptr_ CalendarViewBaseItem** ppContainer) +{ + HRESULT hr = S_OK; + ctl.ComPtr spContainer; + + IFC(ctl.new CalendarViewItem(&spContainer)); + + IFC(spContainer.CopyTo(ppContainer)); + +Cleanup: + return hr; +} + + +_Check_return_ IFACEMETHODIMP PrepareItemContainer( + xaml.IDependencyObject* pContainer, + IInspectable* pItem) +{ + HRESULT hr = S_OK; + wf.DateTime date; + ctl.ComPtr spContainer; + + spContainer = (CalendarViewItem*)(pContainer); + + IFC(ctl.do_get_value(date, pItem)); + IFC(GetCalendar().SetDateTime(date)); + IFC(spContainer.put_Date(date)); + + // maintext + { + wrl_wrappers.HString mainText; + wrl_wrappers.HString automationName; + + IFC(GetCalendar().MonthAsFullString( + automationName.GetAddressOf())); + + IFC(AutomationProperties.SetNameStatic(spContainer.Cast(), automationName.Get())); + + IFC(GetCalendar().MonthAsString( + 0, /*idealLength, set to 0 to get the abbreviated string*/ + mainText.GetAddressOf())); + + IFC(spContainer.UpdateMainText(mainText.Get())); + } + + // label text + { + BOOLEAN isLabelVisible = FALSE; + + IFC(GetOwner().get_IsGroupLabelVisible(&isLabelVisible)); + + IFC(UpdateLabel(spContainer.Get(), !!isLabelVisible)); + } + + // today state will be updated in CalendarViewGeneratorHost.PrepareItemContainer + + // YearView doesn't have selection state + + // Make a grid effect on YearView. + // For MonthView, we put a margin on CalendarViewDayItem in the template to achieve the grid effect. + // For YearView and DecadeView, we can't do the same because there is no template for MonthItem and YearItem + { + xaml.Thickness margin{ 1.0, 1.0, 1.0, 1.0 }; + IFC(spContainer.put_Margin(margin)); + } + + //This code enables the focus visuals on the CalendarViewItems in the Year Pane in the correct position. + { + const xaml.Thickness focusMargin{ -2.0, -2.0, -2.0, -2.0 }; + IFC(spContainer.put_FocusVisualMargin(focusMargin)); + + IFC(spContainer.put_UseSystemFocusVisuals(TRUE)); + } + + IFC(CalendarViewGeneratorHost.PrepareItemContainer(pContainer, pItem)); + +Cleanup: + return hr; +} + +_Check_return_ HRESULT UpdateLabel( CalendarViewBaseItem* pItem, bool isLabelVisible) +{ + bool showLabel = false; + if (isLabelVisible) + { + wf.DateTime date; + var pCalendar = GetCalendar(); + int month = 0; + int firstMonthOfThisYear = 0; + + // TODO: consider caching the firstday flag because we also need this information when determining snap points + // (however Decadeview doesn't need this for Label). + IFC_RETURN(pItem.GetDate(&date)); + IFC_RETURN(pCalendar.SetDateTime(date)); + IFC_RETURN(pCalendar.get_FirstMonthInThisYear(&firstMonthOfThisYear)); + IFC_RETURN(pCalendar.get_Month(&month)); + + showLabel = firstMonthOfThisYear == month; + + if (showLabel) + { + wrl_wrappers.HString labelText; + IFC_RETURN(pCalendar.YearAsString(labelText.GetAddressOf())); + IFC_RETURN(pItem.UpdateLabelText(labelText.Get())); + } + } + IFC_RETURN(pItem.ShowLabelText(showLabel)); + return S_OK; +} + +_Check_return_ HRESULT GetIsFirstItemInScope( int index, out bool* pIsFirstItemInScope) +{ + HRESULT hr = S_OK; + + *pIsFirstItemInScope = false; + if (index == 0) + { + *pIsFirstItemInScope = true; + } + else + { + wf.DateTime date = {}; + int month = 0; + int firstMonth = 0; + + IFC(GetDateAt(index, &date)); + var pCalendar = GetCalendar(); + IFC(pCalendar.SetDateTime(date)); + IFC(pCalendar.get_Month(&month)); + IFC(pCalendar.get_FirstMonthInThisYear(&firstMonth)); + *pIsFirstItemInScope = month == firstMonth; + } + +Cleanup: + return hr; +} + +_Check_return_ HRESULT GetUnit(out int* pValue) +{ + return GetCalendar().get_Month(pValue); +} + +_Check_return_ HRESULT SetUnit( int value) +{ + return GetCalendar().put_Month(value); +} + +_Check_return_ HRESULT AddUnits( int value) +{ + return GetCalendar().AddMonths(value); +} + +_Check_return_ HRESULT AddScopes( int value) +{ + IFC_RETURN(GetCalendar().AddYears(value)); + return S_OK; +} + +_Check_return_ HRESULT GetFirstUnitInThisScope(out int* pValue) +{ + return GetCalendar().get_FirstMonthInThisYear(pValue); +} +_Check_return_ HRESULT GetLastUnitInThisScope(out int* pValue) +{ + return GetCalendar().get_LastMonthInThisYear(pValue); +} + +_Check_return_ HRESULT OnScopeChanged() +{ + return GetOwner().FormatYearName(m_maxDateOfCurrentScope, m_pHeaderText.ReleaseAndGetAddressOf()); +} + +_Check_return_ HRESULT GetPossibleItemStrings(_Outptr_ const std.vector** ppStrings) +{ + *ppStrings = &m_possibleItemStrings; + + if (m_possibleItemStrings.empty()) + { + // for all known calendar identifiers so far (10 different calendar identifiers), we can find the longest year in no more than 3 years + // if we start from min date of this calendar. + + // below are the longest year and the lowest index of that year we found for each calendar identifier. + // we hope that any new calendar in the future don't break this rule. + + // PersianCalendar, maxLength = 12 @ index 0 + // GregorianCalendar, maxLength = 12 @ index 0 + // HebrewCalendar, maxLength = 13 @ index 2 + // HijriCalendar, maxLength = 12 @ index 0 + // JapaneseCalendar, maxLength = 12 @ index 0 + // JulianCalendar, maxLength = 12 @ index 0 + // KoreanCalendar, maxLength = 12 @ index 0 + // TaiwanCalendar, maxLength = 12 @ index 0 + // ThaiCalendar, maxLength = 12 @ index 1 + // UmAlQuraCalendar, maxLength = 12 @ index 0 + { + const int MaxNumberOfYearsToBeChecked = 3; + wf.DateTime longestYear; + int lengthOfLongestYear = 0; + int numberOfMonths = 0; + int month = 0; + + var pCalendar = GetCalendar(); + + IFC_RETURN(pCalendar.SetToMin()); + for (int i = 0; i < MaxNumberOfYearsToBeChecked; i++) + { + IFC_RETURN(pCalendar.get_NumberOfMonthsInThisYear(&numberOfMonths)); + if (numberOfMonths > lengthOfLongestYear) + { + lengthOfLongestYear = numberOfMonths; + IFC_RETURN(pCalendar.GetDateTime(&longestYear)); + } + IFC_RETURN(pCalendar.AddYears(1)); + } + + ASSERT(lengthOfLongestYear == 13 || lengthOfLongestYear == 12); + IFC_RETURN(pCalendar.SetDateTime(longestYear)); + IFC_RETURN(pCalendar.get_FirstMonthInThisYear(&month)); + IFC_RETURN(pCalendar.put_Month(month)); + + m_possibleItemStrings.reserve(lengthOfLongestYear); + + for (int i = 0; i < lengthOfLongestYear; i++) + { + wrl_wrappers.HString string; + + IFC_RETURN(pCalendar.MonthAsString( + 0, /*idealLength, set to 0 to get the abbreviated string*/ + string.GetAddressOf())); + m_possibleItemStrings.emplace_back(std.move(string)); + IFC_RETURN(pCalendar.AddMonths(1)); + } + } + } + + return S_OK; +} + +_Check_return_ HRESULT CompareDate( wf.DateTime lhs, wf.DateTime rhs, out int* pResult) +{ + return GetOwner().GetDateComparer().CompareMonth(lhs, rhs, pResult); +} + +INT64 GetAverageTicksPerUnit() +{ + // this is being used to estimate the distance between two dates, + // it doesn't need to be (and it can't be) the exact value + return CalendarConstants.s_ticksPerDay * 365 / 12; +} \ No newline at end of file diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewItemAutomationPeer_Partial.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewItemAutomationPeer_Partial.cs new file mode 100644 index 000000000000..29e22d7d382a --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarViewItemAutomationPeer_Partial.cs @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#include "precomp.h" +#include "CalendarViewItemAutomationPeer.g.h" +#include "CalendarViewItem.g.h" +#include "CalendarView.g.h" +#include "CalendarViewGeneratorHost.h" +#include "CalendarPanel.g.h" + +using namespace DirectUI; +using namespace DirectUISynonyms; + +IFACEMETHODIMP CalendarViewItemAutomationPeer.GetPatternCore( xaml_automation_peers.PatternInterface patternInterface, _Outptr_ IInspectable** ppReturnValue) +{ + IFCPTR_RETURN(ppReturnValue); + *ppReturnValue = null; + + if (patternInterface == xaml_automation_peers.PatternInterface_Invoke || + patternInterface == xaml_automation_peers.PatternInterface_TableItem) + { + *ppReturnValue = ctl.as_iinspectable(this); + ctl.addref_interface(this); + } + else + { + IFC_RETURN(CalendarViewItemAutomationPeerGenerated.GetPatternCore(patternInterface, ppReturnValue)); + } + return S_OK; +} + +IFACEMETHODIMP CalendarViewItemAutomationPeer.GetClassNameCore(out HSTRING* pReturnValue) +{ + IFCPTR_RETURN(pReturnValue); + IFC_RETURN(wrl_wrappers.Hstring(STR_LEN_PAIR("CalendarViewItem")).CopyTo(pReturnValue)); + return S_OK; +} + +IFACEMETHODIMP CalendarViewItemAutomationPeer.GetAutomationControlTypeCore(out xaml_automation_peers.AutomationControlType* pReturnValue) +{ + IFCPTR_RETURN(pReturnValue); + *pReturnValue = xaml_automation_peers.AutomationControlType_Button; + + return S_OK; +} + +_Check_return_ HRESULT CalendarViewItemAutomationPeer.InvokeImpl() +{ + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + CalendarView *pParent = spOwner.Cast().GetParentCalendarView(); + IFCPTR_RETURN(pParent); + IFC_RETURN(pParent.OnSelectMonthYearItem(spOwner.Cast(), xaml.FocusState.FocusState_Keyboard)); + + return S_OK; +} + +_Check_return_ HRESULT CalendarViewItemAutomationPeer.get_ColumnImpl(out INT* pValue) +{ + IFCPTR_RETURN(pValue); + *pValue = 0; + + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + wf.DateTime date; + IFC_RETURN(spOwner.Cast().GetDate(&date)); + + CalendarView *pParent = spOwner.Cast().GetParentCalendarView(); + IFCPTR_RETURN(pParent); + + ctl.ComPtr spHost; + IFC_RETURN(pParent.GetActiveGeneratorHost(&spHost)); + + CalendarPanel* pCalendarPanel = spHost.GetPanel(); + if (pCalendarPanel) + { + int itemIndex = 0; + IFC_RETURN(spHost.CalculateOffsetFromMinDate(date, &itemIndex)); + + int cols = 1; + IFC_RETURN(pCalendarPanel.get_Cols(&cols)); + int firstVisibleIndex = 0; + IFC_RETURN(pCalendarPanel.get_FirstVisibleIndex(&firstVisibleIndex)); + + *pValue = (itemIndex - firstVisibleIndex) % cols; + if (*pValue < 0) + { + *pValue += cols; + } + } + return S_OK; +} + +_Check_return_ HRESULT CalendarViewItemAutomationPeer.get_RowImpl(out INT* pValue) +{ + IFCPTR_RETURN(pValue); + *pValue = 0; + + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + wf.DateTime date; + IFC_RETURN(spOwner.Cast().GetDate(&date)); + + CalendarView *pParent = spOwner.Cast().GetParentCalendarView(); + IFCPTR_RETURN(pParent); + + ctl.ComPtr spHost; + IFC_RETURN(pParent.GetActiveGeneratorHost(&spHost)); + + CalendarPanel* pCalendarPanel = spHost.GetPanel(); + if (pCalendarPanel) + { + int itemIndex = 0; + IFC_RETURN(spHost.CalculateOffsetFromMinDate(date, &itemIndex)); + + int cols = 1; + IFC_RETURN(pCalendarPanel.get_Cols(&cols)); + int firstVisibleIndex = 0; + IFC_RETURN(pCalendarPanel.get_FirstVisibleIndex(&firstVisibleIndex)); + + // Find the relative row position w.r.to visible rows + *pValue = (itemIndex - firstVisibleIndex) / cols; + // the element is not visible and we can't define row + if (*pValue < 0) + { + return E_NOT_SUPPORTED; + } + } + return S_OK; +} + +_Check_return_ HRESULT CalendarViewItemAutomationPeer.GetColumnHeaderItemsImpl(out UINT* pReturnValueCount, _Out_writes_to_ptr_(*pReturnValueCount) xaml_automation.Provider.IIRawElementProviderSimple*** ppReturnValue) +{ + *pReturnValueCount = 0; + return S_OK; +} + +_Check_return_ HRESULT CalendarViewItemAutomationPeer.GetRowHeaderItemsImpl(out UINT* pReturnValueCount, _Out_writes_to_ptr_(*pReturnValueCount) xaml_automation.Provider.IIRawElementProviderSimple*** ppReturnValue) +{ + ctl.ComPtr spOwner; + IFC_RETURN(get_Owner(&spOwner)); + + CalendarViewItem* item = spOwner.Cast(); + CalendarView *pParent = item.GetParentCalendarView(); + IFCPTR_RETURN(pParent); + + wf.DateTime itemDate; + IFC_RETURN(item.GetDate(&itemDate)); + + // Currently we only want this row header read in year mode, not in decade mode. + IFC_RETURN(pParent.GetRowHeaderForItemAutomationPeer(itemDate, xaml_controls.CalendarViewDisplayMode_Year, pReturnValueCount, ppReturnValue)); + + return S_OK; +} \ No newline at end of file diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarView_Partial.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarView_Partial.cs new file mode 100644 index 000000000000..b6f64b7f4300 --- /dev/null +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/CalendarView/CalendarView_Partial.cs @@ -0,0 +1,2720 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Windows.Globalization; +using Windows.Globalization.DateTimeFormatting; +using Windows.UI.Xaml.Controls.Primitives; +using DayOfWeek = Windows.Globalization.DayOfWeek; + +namespace Windows.UI.Xaml.Controls +{ + public class CalendarView + { + List m_tpSelectedDates; + + Button m_tpHeaderButton; + Button m_tpPreviousButton; + Button m_tpNextButton; + + Grid m_tpViewsGrid; + + Calendar m_tpCalendar; + DateTimeFormatter m_tpMonthYearFormatter; + DateTimeFormatter m_tpYearFormatter; + + CalendarViewGeneratorHost m_tpMonthViewItemHost; + CalendarViewGeneratorHost m_tpYearViewItemHost; + CalendarViewGeneratorHost m_tpDecadeViewItemHost; + + ScrollViewer m_tpMonthViewScrollViewer; + ScrollViewer m_tpYearViewScrollViewer; + ScrollViewer m_tpDecadeViewScrollViewer; + + // we define last displayed date by following: + // 1. if the last displayed item is visible, the date of last displayed item + // 2. if last focused item is not visible, we use the first_visible_inscope_date + // 3. when an item gets focused, we use the date of the focused item. + // + // the default last displayed date will be determined by following: + // 1. display Date if it is requested, if it is not requested, then + // 2. Today, if Today is not in given min/max range, then + // 3. the closest date to Today (i.e. the coerced date of Today) + DateTime m_lastDisplayedDate; + + DateTime m_today; + // m_minDate and m_maxDate are effective min/max dates, which could be different + // than the values return from get_MinDate/get_MaxDate. + // because developer could set a minDate or maxDate that doesn't exist in + // current calendarsystem. (e.g. UmAlQuraCalendar doesn't have 1/1/2099). + DateTime m_maxDate; + DateTime m_minDate; + + // the weekday of mindate. + DayOfWeek m_weekDayOfMinDate; + + TrackerPtr m_tpTemplateSettings; + + ctl::EventPtr m_epHeaderButtonClickHandler; + ctl::EventPtr m_epPreviousButtonClickHandler; + ctl::EventPtr m_epNextButtonClickHandler; + + ctl::EventPtr m_epMonthViewScrollViewerKeyDownEventHandler; + ctl::EventPtr m_epYearViewScrollViewerKeyDownEventHandler; + ctl::EventPtr m_epDecadeViewScrollViewerKeyDownEventHandler; + + const int s_minNumberOfWeeks = 2; + const int s_maxNumberOfWeeks = 8; + const int s_defaultNumberOfWeeks = 6; + const int s_numberOfDaysInWeek = 7; + + int m_colsInYearDecadeView; // default value is 4 + + int m_rowsInYearDecadeView; // default value is 4 + + // if we decide to have a different startIndex in YearView or DecadeView, we should make a corresponding change at CalendarViewItemAutomationPeer::get_ColumnImpl + // in MonthView, because we can set the DayOfWeek property, the first item is not always start from the first positon inside the Panel + int m_monthViewStartIndex; + + // dayOfWeekNames stores abbreviated names of each day of the week. dayOfWeekNamesFull stores the full name to be read aloud by accessibility. + List m_dayOfWeekNames; + List m_dayOfWeekNamesFull; + + IEnumerable m_tpCalendarLanguages; + + ctl::EventPtr m_epSelectedDatesChangedHandler; + + + // the keydown event args from CalendarItem. + ctl::WeakRefPtr m_wrKeyDownEventArgsFromCalendarItem; + + // the focus state we need to set on the calendaritem after we change the display mode. + FocusState m_focusStateAfterDisplayModeChanged; + + DateComparer m_dateComparer; + + // + // Automation fields + // + + // When Narrator gives focus to a day item, we expect it to read the month header + // (assuming the focus used to be outside or on a day item at a different month). + // During this focus transition, Narrator expects to be able to query the previous peer (if any). + // So we need to keep track of the current and previous month header peers. + CalendarViewHeaderAutomationPeer m_currentHeaderPeer; + CalendarViewHeaderAutomationPeer m_previousHeaderPeer; + + // when mindate or maxdate changed, we set this flag + bool m_dateSourceChanged; + + // when calendar identifier changed, we set this flag + bool m_calendarChanged; + + bool m_itemHostsConnected; + + bool m_areDirectManipulationStateChangeHandlersHooked; + + // this flag indicts the change of SelectedDates comes from internal or external. + bool m_isSelectedDatesChanginginternally; + + // when true we need to move focus to a calendaritem after we change the display mode. + bool m_focusItemAfterDisplayModeChanged; + + bool m_isMultipleEraCalendar; + + bool m_isSetDisplayDateRequested; + + bool m_areYearDecadeViewDimensionsSet; + + // After navigationbutton clicked, the head text doesn't change immediately. so we use this flag to tell if the update text is from navigation button + bool m_isNavigationButtonClicked; + + public CalendarView() + { + m_dateSourceChanged = true; + m_calendarChanged = false; + m_itemHostsConnected = false; + m_areYearDecadeViewDimensionsSet = false; + m_colsInYearDecadeView = 4; + m_rowsInYearDecadeView = 4; + m_monthViewStartIndex = 0; + m_weekDayOfMinDate = DayOfWeek.Sunday; + m_isSelectedDatesChanginginternally = false; + m_focusItemAfterDisplayModeChanged = false; + m_focusStateAfterDisplayModeChanged = FocusState.Programmatic; + m_isMultipleEraCalendar = false; + m_areDirectManipulationStateChangeHandlersHooked = false; + m_isSetDisplayDateRequested = true; // by default there is a displayDate request, which is m_lastDisplayedDate + m_isNavigationButtonClicked = false; + + m_today.UniversalTime = 0; + m_maxDate.UniversalTime = 0; + m_minDate.UniversalTime = 0; + m_lastDisplayedDate.UniversalTime = 0; + } + + ~CalendarView() + { + VERIFYHR(DetachButtonClickedEvents(); + VERIFYHR(DetachHandler(m_epSelectedDatesChangedHandler, m_tpSelectedDates); + VERIFYHR(DetachScrollViewerKeyDownEvents(); + + IList selectedDates; + if (m_tpSelectedDates.TryGetSafeReference(&selectedDates)) + { + ((TrackableDateCollection)selectedDates).SetCollectionChangingCallback(null); + } + } + + private void PrepareState() + { + HRESULT hr = S_OK; + + CalendarViewGenerated.PrepareState(); + + { + m_dateComparer.reset(new DateComparer(); + + ctl.ComPtr spSelectedDates; + + ctl.make(&spSelectedDates); + + m_epSelectedDatesChangedHandler.AttachEventHandler(spSelectedDates, + [this](wfc.IObservableVector < wf.DateTime > *pSender, wfc.IVectorChangedEventArgs * pArgs) + { + return OnSelectedDatesChanged(pSender, pArgs); + }); + + spSelectedDates.SetCollectionChangingCallback( + [this](TrackableDateCollection_CollectionChanging action, wf.DateTime addingDate) + { + return OnSelectedDatesChanging(action, addingDate); + }); + + m_tpSelectedDates = spSelectedDates; + put_SelectedDates(spSelectedDates); + } + + { + ctl.ComPtr spMonthViewItemHost; + ctl.ComPtr spYearViewItemHost; + ctl.ComPtr spDecadeViewItemHost; + + ctl.make(&spMonthViewItemHost); + m_tpMonthViewItemHost = spMonthViewItemHost; + m_tpMonthViewItemHost.SetOwner(this); + + ctl.make(&spYearViewItemHost); + m_tpYearViewItemHost = spYearViewItemHost; + m_tpYearViewItemHost.SetOwner(this); + + ctl.make(&spDecadeViewItemHost); + m_tpDecadeViewItemHost = spDecadeViewItemHost; + m_tpDecadeViewItemHost.SetOwner(this); + } + + { + CreateCalendarLanguages(); + CreateCalendarAndMonthYearFormatter(); + } + + { + ctl.ComPtr spTemplateSettings; + + ctl.make(&spTemplateSettings); + + spTemplateSettings.HasMoreViews = TRUE; + put_TemplateSettings(spTemplateSettings); + m_tpTemplateSettings = spTemplateSettings; + } + + Cleanup: + return hr; + } + + // Override the GetDefaultValue method to return the default values + // for Hub dependency properties. + private void GetDefaultValue2( + DependencyProperty pDP, + out CValue* pValue) + { + HRESULT hr = S_OK; + + IFCPTR(pDP); + IFCPTR(pValue); + + switch (pDP.GetIndex()) + { + case KnownPropertyIndex.CalendarView_CalendarIdentifier: + pValue.SetString(wrl_wrappers.string(STR_LEN_PAIR("GregorianCalendar"))); + break; + case KnownPropertyIndex.CalendarView_NumberOfWeeksInView: + pValue.SetSigned(s_defaultNumberOfWeeks); + break; + default: + CalendarViewGenerated.GetDefaultValue2(pDP, pValue); + break; + } + } + + // Basically these Alignment properties only affect Arrange, but in CalendarView + // the item size and Panel size are also affected when we change the property from + // stretch to unstretch, or vice versa. In these cases we need to invalidate panels' measure. + private void OnAlignmentChanged(DependencyPropertyChangedEventArgs args) + { + uint oldAlignment = 0; + uint newAlignment = 0; + bool isOldStretched = false; + bool isNewStretched = false; + + oldAlignment = (uint)args.OldValue; + newAlignment = (uint)args.NewValue; + + switch (args.m_pDP.GetIndex()) + { + case KnownPropertyIndex.Control_HorizontalContentAlignment: + case KnownPropertyIndex.FrameworkElement_HorizontalAlignment: + isOldStretched = (xaml.HorizontalAlignment)(oldAlignment) == xaml.HorizontalAlignment_Stretch; + isNewStretched = (xaml.HorizontalAlignment)(newAlignment) == xaml.HorizontalAlignment_Stretch; + break; + case KnownPropertyIndex.Control_VerticalContentAlignment: + case KnownPropertyIndex.FrameworkElement_VerticalAlignment: + isOldStretched = (xaml.VerticalAlignment)(oldAlignment) == xaml.VerticalAlignment_Stretch; + isNewStretched = (xaml.VerticalAlignment)(newAlignment) == xaml.VerticalAlignment_Stretch; + break; + default: + ASSERT(false); + break; + } + + if (isOldStretched != isNewStretched) + { + ForeachHost([](CalendarViewGeneratorHost * pHost) + { + var pPanel = pHost.Panel; + if (pPanel) + { + pPanel.InvalidateMeasure(); + } + + return S_OK; + }); + } + + return S_OK; + } + + // Handle the custom property changed event and call the OnPropertyChanged methods. + private void OnPropertyChanged2( + const DependencyPropertyChangedEventArgs args) + { + + CalendarViewGenerated.OnPropertyChanged2(args); + + switch (args.m_pDP.GetIndex()) + { + case KnownPropertyIndex.Control_HorizontalContentAlignment: + case KnownPropertyIndex.Control_VerticalContentAlignment: + case KnownPropertyIndex.FrameworkElement_HorizontalAlignment: + case KnownPropertyIndex.FrameworkElement_VerticalAlignment: + OnAlignmentChanged(args); + break; + case KnownPropertyIndex.CalendarView_MinDate: + case KnownPropertyIndex.CalendarView_MaxDate: + m_dateSourceChanged = true; + InvalidateMeasure(); + break; + case KnownPropertyIndex.FrameworkElement_Language: + // Globlization.Calendar doesn't support changing languages, so when languages changed, + // we have to create a new Globalization.Calendar, and also we'll update the date source so + // the change of languages can take effect on the existing items. + CreateCalendarLanguages(); + // fall through + case KnownPropertyIndex.CalendarView_CalendarIdentifier: + m_calendarChanged = true; + m_dateSourceChanged = true; //calendarid changed, even if the mindate or maxdate is not changed we still need to regenerate all calendar items. + InvalidateMeasure(); + break; + case KnownPropertyIndex.CalendarView_NumberOfWeeksInView: + { + int rows = 0; + args.NewValue.GetSigned(rows); + + if (rows < s_minNumberOfWeeks || rows > s_maxNumberOfWeeks) + { + ErrorHelper.OriginateErrorUsingResourceID(E_FAIL, ERROR_CALENDAR_NUMBER_OF_WEEKS_OUTOFRANGE); + } + + if (m_tpMonthViewItemHost.Panel) + { + m_tpMonthViewItemHost.Panel.SetSuggestedDimension(s_numberOfDaysInWeek, rows); + } + } + break; + case KnownPropertyIndex.CalendarView_DayOfWeekFormat: + FormatWeekDayNames(); + // fall through + case KnownPropertyIndex.CalendarView_FirstDayOfWeek: + UpdateWeekDayNames(); + break; + case KnownPropertyIndex.CalendarView_SelectionMode: + OnSelectionModeChanged(); + break; + case KnownPropertyIndex.CalendarView_IsOutOfScopeEnabled: + OnIsOutOfScopePropertyChanged(); + break; + case KnownPropertyIndex.CalendarView_DisplayMode: + { + uint oldDisplayMode = 0; + uint newDisplayMode = 0; + + oldDisplayMode = (uint)args.OldValue; + newDisplayMode = (uint)args.NewValue; + + OnDisplayModeChanged( + (CalendarViewDisplayMode)(oldDisplayMode), + (CalendarViewDisplayMode)(newDisplayMode) + ); + } + break; + case KnownPropertyIndex.CalendarView_IsTodayHighlighted: + OnIsTodayHighlightedPropertyChanged(); + break; + case KnownPropertyIndex.CalendarView_IsGroupLabelVisible: + OnIsLabelVisibleChanged(); + break; + + // To reduce memory usage, we move lots font/brush properties from CalendarViewItem to CalendarView, + // the cost is we can't benefit from property system to invalidate measure/render automatically. + // However changing these font/brush properties is not a frequent scenario. So once they are changed + // we'll manually update the items. + // Basically we should only update those affected items (e.g. when PressedBackground changed, we should only update + // the item which is being pressed) but to make the code simple we'll update all realized item, unless + // we see performance issue here. + + // Border brushes and Background (they are chromes) will take effect in next Render walk. + case KnownPropertyIndex.CalendarView_FocusBorderBrush: + case KnownPropertyIndex.CalendarView_SelectedHoverBorderBrush: + case KnownPropertyIndex.CalendarView_SelectedPressedBorderBrush: + case KnownPropertyIndex.CalendarView_SelectedBorderBrush: + case KnownPropertyIndex.CalendarView_HoverBorderBrush: + case KnownPropertyIndex.CalendarView_PressedBorderBrush: + case KnownPropertyIndex.CalendarView_CalendarItemBorderBrush: + case KnownPropertyIndex.CalendarView_OutOfScopeBackground: + case KnownPropertyIndex.CalendarView_CalendarItemBackground: + ForeachHost(pHost => + { + ForeachChildInPanel( + pHost.Panel, + pItem => + { + return pItem.InvalidateRender(); + }); + }); + break; + + // Foreground will take effect immediately + case KnownPropertyIndex.CalendarView_PressedForeground: + case KnownPropertyIndex.CalendarView_TodayForeground: + case KnownPropertyIndex.CalendarView_BlackoutForeground: + case KnownPropertyIndex.CalendarView_SelectedForeground: + case KnownPropertyIndex.CalendarView_OutOfScopeForeground: + case KnownPropertyIndex.CalendarView_CalendarItemForeground: + ForeachHost(pHost => + { + ForeachChildInPanel( + pHost.Panel, + pItem => + { + return pItem.UpdateTextBlockForeground(); + }); + }); + break; + + case KnownPropertyIndex.CalendarView_TodayFontWeight: + { + ForeachHost(pHost => + { + var pPanel = pHost.Panel; + + if (pPanel) + { + int indexOfToday = -1; + + indexOfToday = pHost.CalculateOffsetFromMinDate(m_today); + + if (indexOfToday != -1) + { + DependencyObject spChildAsIDO; + CalendarViewBaseItem spChildAsI; + + pPanel.ContainerFromIndex(indexOfToday, &spChildAsIDO); + spChildAsI = spChildAsIDO as ICalendarViewBaseItem; + // today item is realized already, we need to update the state here. + // if today item is not realized yet, we'll update the state when today item is being prepared. + if (spChildAsI is {}) + { + CalendarViewBaseItem spChild; + + spChild = (CalendarViewBaseItem)spChildAsI; + spChild.UpdateTextBlockFontProperties(); + } + } + } + }); + + break; + } + + // Font properties for DayItem (affect measure and arrange) + case KnownPropertyIndex.CalendarView_DayItemFontFamily: + case KnownPropertyIndex.CalendarView_DayItemFontSize: + case KnownPropertyIndex.CalendarView_DayItemFontStyle: + case KnownPropertyIndex.CalendarView_DayItemFontWeight: + { + // if these DayItem properties changed, we need to re-determine the + // biggest dayitem in monthPanel, which will invalidate monthpanel's measure + var pMonthPanel = m_tpMonthViewItemHost.Panel; + if (pMonthPanel) + { + pMonthPanel.SetNeedsToDetermineBiggestItemSize(); + } + + } + // fall through + + // Font properties for MonthLabel (they won't affect measure or arrange) + case KnownPropertyIndex.CalendarView_FirstOfMonthLabelFontFamily: + case KnownPropertyIndex.CalendarView_FirstOfMonthLabelFontSize: + case KnownPropertyIndex.CalendarView_FirstOfMonthLabelFontStyle: + case KnownPropertyIndex.CalendarView_FirstOfMonthLabelFontWeight: + ForeachChildInPanel( + m_tpMonthViewItemHost.Panel, + pItem => + { + return pItem.UpdateTextBlockFontProperties(); + }); + break; + + // Font properties for MonthYearItem + case KnownPropertyIndex.CalendarView_MonthYearItemFontFamily: + case KnownPropertyIndex.CalendarView_MonthYearItemFontSize: + case KnownPropertyIndex.CalendarView_MonthYearItemFontStyle: + case KnownPropertyIndex.CalendarView_MonthYearItemFontWeight: + { + // these properties will affect MonthItem and YearItem's size, so we should + // tell their panels to re-determine the biggest item size. + std.array < CalendarPanel *, 2 > pPanels{ + { + m_tpYearViewItemHost.Panel, m_tpDecadeViewItemHost.Panel + } + } + ; + + for (var i = 0; i < pPanels.size(); ++i) + { + if (pPanels[i]) + { + pPanels[i].SetNeedsToDetermineBiggestItemSize(); + } + } + } + // fall through + case KnownPropertyIndex.CalendarView_FirstOfYearDecadeLabelFontFamily: + case KnownPropertyIndex.CalendarView_FirstOfYearDecadeLabelFontSize: + case KnownPropertyIndex.CalendarView_FirstOfYearDecadeLabelFontStyle: + case KnownPropertyIndex.CalendarView_FirstOfYearDecadeLabelFontWeight: + { + std.array < CalendarPanel *, 2 > pPanels{ + { + m_tpYearViewItemHost.Panel, m_tpDecadeViewItemHost.Panel + } + } + ; + + for (var i = 0; i < pPanels.size(); ++i) + { + ForeachChildInPanel(pPanels[i], + [this](CalendarViewBaseItem * pItem) + { + return pItem.UpdateTextBlockFontProperties(); + }); + + } + + break; + } + // Alignments affect DayItem only + case KnownPropertyIndex.CalendarView_HorizontalDayItemAlignment: + case KnownPropertyIndex.CalendarView_VerticalDayItemAlignment: + case KnownPropertyIndex.CalendarView_HorizontalFirstOfMonthLabelAlignment: + case KnownPropertyIndex.CalendarView_VerticalFirstOfMonthLabelAlignment: + + ForeachChildInPanel( + m_tpMonthViewItemHost.Panel, + pItem => + { + pItem.UpdateTextBlockAlignments(); + }); + + break; + + // border thickness affects measure (and arrange) + case KnownPropertyIndex.CalendarView_CalendarItemBorderThickness: + ForeachHost(pHost => + { + ForeachChildInPanel( + pHost.Panel, + pItem => + { + pItem.InvalidateMeasure(); + }); + }); + break; + + // Dayitem style changed, update style for all existing day items. + case KnownPropertyIndex.CalendarView_CalendarViewDayItemStyle: + { + Style spStyle; + + ctl.do_query_interface(spStyle, args.NewValueOuterNoRef); + var pMonthPanel = m_tpMonthViewItemHost.Panel; + + ForeachChildInPanel( + pMonthPanel, + pItem => + { + return pItem.SetDayItemStyle(spStyle); + }); + + // Some properties could affect dayitem size (e.g. Dayitem font properties, dayitem size), + // when anyone of them is changed, we need to re-determine the biggest day item. + // This is not a frequent scenario so we can simply set below flag and invalidate measure. + + if (pMonthPanel) + { + pMonthPanel.SetNeedsToDetermineBiggestItemSize(); + } + + break; + } + } + } + + protected override void OnApplyTemplate() + { + CalendarPanel spMonthViewPanel; + CalendarPanel spYearViewPanel; + CalendarPanel spDecadeViewPanel; + Button spHeaderButton; + Button spPreviousButton; + Button spNextButton; + Grid spViewsGrid; + ScrollViewer spMonthViewScrollViewer; + ScrollViewer spYearViewScrollViewer; + ScrollViewer spDecadeViewScrollViewer; + string strAutomationName; + + DetachVisibleIndicesUpdatedEvents(); + DetachButtonClickedEvents(); + DetachScrollViewerFocusEngagedEvents(); + DetachScrollViewerKeyDownEvents(); + + // This will clean up the panels and clear the children + DisconnectItemHosts(); + + if (m_areDirectManipulationStateChangeHandlersHooked) + { + m_areDirectManipulationStateChangeHandlersHooked = false; + ForeachHost(pHost => + { + var pScrollViewer = pHost.ScrollViewer; + if (pScrollViewer) + { + pScrollViewer.SetDirectManipulationStateChangeHandler(null); + } + }); + } + + ForeachHost(pHost => + { + pHost.Panel = null; + pHost.ScrollViewer = null; + }); + + + m_tpHeaderButton.Clear(); + m_tpPreviousButton.Clear(); + m_tpNextButton.Clear(); + m_tpViewsGrid.Clear(); + + CalendarViewGenerated.OnApplyTemplate(); + + spMonthViewPanel = this.GetTemplatePart("MonthViewPanel"); + spYearViewPanel = this.GetTemplatePart("YearViewPanel"); + spDecadeViewPanel = this.GetTemplatePart("DecadeViewPanel"); + + m_tpMonthViewItemHost.Panel = spMonthViewPanel; + m_tpYearViewItemHost.Panel = spYearViewPanel; + m_tpDecadeViewItemHost.Panel = spDecadeViewPanel; + + if (spMonthViewPanel is {}) + { + CalendarPanel pPanel = (CalendarPanel)spMonthViewPanel; + int numberOfWeeksInView = 0; + + // MonthView panel is the only primary panel (and never changes) + pPanel.PanelType = CalendarPanelType.Primary; + + numberOfWeeksInView = NumberOfWeeksInView; + pPanel.SetSuggestedDimension(s_numberOfDaysInWeek, numberOfWeeksInView); + pPanel.Orientation = Orientation.Horizontal; + } + + if (spYearViewPanel is {}) + { + CalendarPanel pPanel = (CalendarPanel)spYearViewPanel; + + // YearView panel is a Secondary_SelfAdaptive panel by default + if (!m_areYearDecadeViewDimensionsSet) + { + pPanel.PanelType = CalendarPanelType.Secondary_SelfAdaptive; + } + + pPanel.SetSuggestedDimension(m_colsInYearDecadeView, m_rowsInYearDecadeView); + pPanel.Orientation = Orientation.Horizontal; + } + + if (spDecadeViewPaneli is {}) + { + CalendarPanel pPanel = (CalendarPanel)spDecadeViewPanel; + + // DecadeView panel is a Secondary_SelfAdaptive panel by default + if (!m_areYearDecadeViewDimensionsSet) + { + pPanel.PanelType = CalendarPanelType.Secondary_SelfAdaptive; + } + + pPanel.SetSuggestedDimension(m_colsInYearDecadeView, m_rowsInYearDecadeView); + pPanel.Orientation = Orientation.Horizontal; + } + + spHeaderButton = this.GetTemplatePart