|
1 | 1 |
|
2 | 2 | #include "pch.h"
|
3 | 3 | #include "CompositionContextHelper.h"
|
| 4 | +#include <algorithm> |
4 | 5 | #if __has_include("Composition.Experimental.SystemCompositionContextHelper.g.cpp")
|
5 | 6 | #include "Composition.Experimental.SystemCompositionContextHelper.g.cpp"
|
6 | 7 | #endif
|
@@ -74,6 +75,10 @@ struct CompositionTypeTraits<WindowsTypeTag> {
|
74 | 75 | winrt::Windows::UI::Composition::Interactions::InteractionTrackerRequestIgnoredArgs;
|
75 | 76 | using InteractionTrackerValuesChangedArgs =
|
76 | 77 | winrt::Windows::UI::Composition::Interactions::InteractionTrackerValuesChangedArgs;
|
| 78 | + using InteractionTrackerInertiaRestingValue = |
| 79 | + winrt::Windows::UI::Composition::Interactions::InteractionTrackerInertiaRestingValue; |
| 80 | + using InteractionTrackerInertiaModifier = |
| 81 | + winrt::Windows::UI::Composition::Interactions::InteractionTrackerInertiaModifier; |
77 | 82 | using ScalarKeyFrameAnimation = winrt::Windows::UI::Composition::ScalarKeyFrameAnimation;
|
78 | 83 | using ShapeVisual = winrt::Windows::UI::Composition::ShapeVisual;
|
79 | 84 | using SpriteVisual = winrt::Windows::UI::Composition::SpriteVisual;
|
@@ -143,6 +148,10 @@ struct CompositionTypeTraits<MicrosoftTypeTag> {
|
143 | 148 | winrt::Microsoft::UI::Composition::Interactions::InteractionTrackerRequestIgnoredArgs;
|
144 | 149 | using InteractionTrackerValuesChangedArgs =
|
145 | 150 | winrt::Microsoft::UI::Composition::Interactions::InteractionTrackerValuesChangedArgs;
|
| 151 | + using InteractionTrackerInertiaRestingValue = |
| 152 | + winrt::Microsoft::UI::Composition::Interactions::InteractionTrackerInertiaRestingValue; |
| 153 | + using InteractionTrackerInertiaModifier = |
| 154 | + winrt::Microsoft::UI::Composition::Interactions::InteractionTrackerInertiaModifier; |
146 | 155 | using ScalarKeyFrameAnimation = winrt::Microsoft::UI::Composition::ScalarKeyFrameAnimation;
|
147 | 156 | using ShapeVisual = winrt::Microsoft::UI::Composition::ShapeVisual;
|
148 | 157 | using SpriteVisual = winrt::Microsoft::UI::Composition::SpriteVisual;
|
@@ -782,9 +791,13 @@ struct CompScrollerVisual : winrt::implements<
|
782 | 791 | }
|
783 | 792 |
|
784 | 793 | void Horizontal(bool value) noexcept {
|
| 794 | + bool previousHorizontal = m_horizontal; |
785 | 795 | m_horizontal = value;
|
786 | 796 |
|
787 |
| - UpdateInteractionModes(); |
| 797 | + if (previousHorizontal != m_horizontal) { |
| 798 | + UpdateInteractionModes(); |
| 799 | + ConfigureSnapInertiaModifiers(); // Reconfigure modifiers when direction changes |
| 800 | + } |
788 | 801 | }
|
789 | 802 |
|
790 | 803 | void UpdateInteractionModes() noexcept {
|
@@ -855,6 +868,21 @@ struct CompScrollerVisual : winrt::implements<
|
855 | 868 | m_interactionTracker.MinScale(minimumZoomScale);
|
856 | 869 | }
|
857 | 870 |
|
| 871 | + void SetSnapPoints( |
| 872 | + bool snapToStart, |
| 873 | + bool snapToEnd, |
| 874 | + winrt::Windows::Foundation::Collections::IVectorView<float> const &offsets) noexcept { |
| 875 | + m_snapToStart = snapToStart; |
| 876 | + m_snapToEnd = snapToEnd; |
| 877 | + m_snapToOffsets.clear(); |
| 878 | + if (offsets) { |
| 879 | + for (auto const &offset : offsets) { |
| 880 | + m_snapToOffsets.push_back(offset); |
| 881 | + } |
| 882 | + } |
| 883 | + ConfigureSnapInertiaModifiers(); |
| 884 | + } |
| 885 | + |
858 | 886 | void Opacity(float opacity) noexcept {
|
859 | 887 | m_visual.Opacity(opacity);
|
860 | 888 | }
|
@@ -1050,8 +1078,155 @@ struct CompScrollerVisual : winrt::implements<
|
1050 | 1078 | 0});
|
1051 | 1079 | }
|
1052 | 1080 |
|
| 1081 | + void ConfigureSnapInertiaModifiers() noexcept { |
| 1082 | + if (!m_visual || !m_contentVisual || !m_interactionTracker) { |
| 1083 | + return; |
| 1084 | + } |
| 1085 | + |
| 1086 | + auto visualSize = m_visual.Size(); |
| 1087 | + auto contentSize = m_contentVisual.Size(); |
| 1088 | + if (visualSize.x <= 0 || visualSize.y <= 0 || contentSize.x <= 0 || contentSize.y <= 0) { |
| 1089 | + OutputDebugStringW(L"Invalid visual/content size\n"); |
| 1090 | + return; |
| 1091 | + } |
| 1092 | + |
| 1093 | + auto compositor = m_interactionTracker.Compositor(); |
| 1094 | + |
| 1095 | + // Collect and deduplicate all snap positions |
| 1096 | + std::vector<float> snapPositions; |
| 1097 | + |
| 1098 | + if (m_snapToStart) { |
| 1099 | + snapPositions.push_back(0.0f); |
| 1100 | + } |
| 1101 | + |
| 1102 | + snapPositions.insert(snapPositions.end(), m_snapToOffsets.begin(), m_snapToOffsets.end()); |
| 1103 | + std::sort(snapPositions.begin(), snapPositions.end()); |
| 1104 | + snapPositions.erase(std::unique(snapPositions.begin(), snapPositions.end()), snapPositions.end()); |
| 1105 | + |
| 1106 | + std::vector<typename TTypeRedirects::InteractionTrackerInertiaRestingValue> restingValues; |
| 1107 | + |
| 1108 | + for (size_t i = 0; i < snapPositions.size(); ++i) { |
| 1109 | + const auto position = snapPositions[i]; |
| 1110 | + auto restingValue = TTypeRedirects::InteractionTrackerInertiaRestingValue::Create(compositor); |
| 1111 | + |
| 1112 | + winrt::hstring axisComponent = m_horizontal ? L"X" : L"Y"; |
| 1113 | + winrt::hstring conditionExpr; |
| 1114 | + |
| 1115 | + // Build condition expression based on whether there's one or multiple snap points |
| 1116 | + if (snapPositions.size() == 1) { |
| 1117 | + conditionExpr = L"abs(this.Target.NaturalRestingPosition." + axisComponent + L" - snap) < 50"; |
| 1118 | + } else { |
| 1119 | + if (i == 0) { |
| 1120 | + conditionExpr = L"this.Target.NaturalRestingPosition." + axisComponent + L" < midpoint"; |
| 1121 | + } else if (i == snapPositions.size() - 1) { |
| 1122 | + conditionExpr = L"this.Target.NaturalRestingPosition." + axisComponent + L" >= midpoint"; |
| 1123 | + } else { |
| 1124 | + conditionExpr = L"this.Target.NaturalRestingPosition." + axisComponent + |
| 1125 | + L" >= prevMidpoint && this.Target.NaturalRestingPosition." + axisComponent + L" < nextMidpoint"; |
| 1126 | + } |
| 1127 | + } |
| 1128 | + |
| 1129 | + auto conditionAnim = compositor.CreateExpressionAnimation(); |
| 1130 | + conditionAnim.Expression(conditionExpr); |
| 1131 | + |
| 1132 | + if (snapPositions.size() == 1) { |
| 1133 | + conditionAnim.SetScalarParameter(L"snap", position); |
| 1134 | + } else { |
| 1135 | + // Multiple snap points - use range-based conditions |
| 1136 | + if (i == 0) { |
| 1137 | + const auto nextPosition = snapPositions[i + 1]; |
| 1138 | + const auto midpoint = (position + nextPosition) / 2.0f; |
| 1139 | + conditionAnim.SetScalarParameter(L"midpoint", midpoint); |
| 1140 | + } else if (i == snapPositions.size() - 1) { |
| 1141 | + const auto prevPosition = snapPositions[i - 1]; |
| 1142 | + const auto midpoint = (prevPosition + position) / 2.0f; |
| 1143 | + conditionAnim.SetScalarParameter(L"midpoint", midpoint); |
| 1144 | + } else { |
| 1145 | + const auto prevPosition = snapPositions[i - 1]; |
| 1146 | + const auto nextPosition = snapPositions[i + 1]; |
| 1147 | + const auto prevMidpoint = (prevPosition + position) / 2.0f; |
| 1148 | + const auto nextMidpoint = (position + nextPosition) / 2.0f; |
| 1149 | + conditionAnim.SetScalarParameter(L"prevMidpoint", prevMidpoint); |
| 1150 | + conditionAnim.SetScalarParameter(L"nextMidpoint", nextMidpoint); |
| 1151 | + } |
| 1152 | + } |
| 1153 | + |
| 1154 | + restingValue.Condition(conditionAnim); |
| 1155 | + |
| 1156 | + // Resting value simply snaps to this position |
| 1157 | + auto restingAnim = compositor.CreateExpressionAnimation(); |
| 1158 | + restingAnim.Expression(L"snap"); |
| 1159 | + restingAnim.SetScalarParameter(L"snap", position); |
| 1160 | + restingValue.RestingValue(restingAnim); |
| 1161 | + |
| 1162 | + restingValues.push_back(restingValue); |
| 1163 | + } |
| 1164 | + |
| 1165 | + if (m_snapToEnd) { |
| 1166 | + auto endRestingValue = TTypeRedirects::InteractionTrackerInertiaRestingValue::Create(compositor); |
| 1167 | + |
| 1168 | + // Create property sets to dynamically compute content - visual size |
| 1169 | + auto contentSizePropertySet = compositor.CreatePropertySet(); |
| 1170 | + contentSizePropertySet.InsertVector2(L"Size", m_contentVisual.Size()); |
| 1171 | + |
| 1172 | + auto visualSizePropertySet = compositor.CreatePropertySet(); |
| 1173 | + visualSizePropertySet.InsertVector2(L"Size", m_visual.Size()); |
| 1174 | + |
| 1175 | + winrt::hstring endPositionExpr = m_horizontal ? L"max(contentSize.Size.x - visualSize.Size.x, 0)" |
| 1176 | + : L"max(contentSize.Size.y - visualSize.Size.y, 0)"; |
| 1177 | + |
| 1178 | + float prevPosition = snapPositions.empty() ? 0.0f : snapPositions.back(); |
| 1179 | + |
| 1180 | + winrt::hstring endConditionExpr = m_horizontal |
| 1181 | + ? L"this.Target.NaturalRestingPosition.X >= ((max(contentSize.Size.x - visualSize.Size.x, 0) + prevSnap) / 2.0)" |
| 1182 | + : L"this.Target.NaturalRestingPosition.Y >= ((max(contentSize.Size.y - visualSize.Size.y, 0) + prevSnap) / 2.0)"; |
| 1183 | + |
| 1184 | + auto endCondition = compositor.CreateExpressionAnimation(); |
| 1185 | + endCondition.Expression(endConditionExpr); |
| 1186 | + endCondition.SetReferenceParameter(L"contentSize", contentSizePropertySet); |
| 1187 | + endCondition.SetReferenceParameter(L"visualSize", visualSizePropertySet); |
| 1188 | + endCondition.SetScalarParameter(L"prevSnap", prevPosition); |
| 1189 | + |
| 1190 | + auto endResting = compositor.CreateExpressionAnimation(); |
| 1191 | + endResting.Expression(endPositionExpr); |
| 1192 | + endResting.SetReferenceParameter(L"contentSize", contentSizePropertySet); |
| 1193 | + endResting.SetReferenceParameter(L"visualSize", visualSizePropertySet); |
| 1194 | + |
| 1195 | + endRestingValue.Condition(endCondition); |
| 1196 | + endRestingValue.RestingValue(endResting); |
| 1197 | + |
| 1198 | + restingValues.push_back(endRestingValue); |
| 1199 | + } |
| 1200 | + |
| 1201 | + if (!restingValues.empty()) { |
| 1202 | + auto modifiers = winrt::single_threaded_vector<typename TTypeRedirects::InteractionTrackerInertiaModifier>(); |
| 1203 | + for (auto &v : restingValues) { |
| 1204 | + auto modifier = v.as<typename TTypeRedirects::InteractionTrackerInertiaModifier>(); |
| 1205 | + if (modifier) { |
| 1206 | + modifiers.Append(modifier); |
| 1207 | + } |
| 1208 | + } |
| 1209 | + |
| 1210 | + if (m_horizontal) { |
| 1211 | + m_interactionTracker.ConfigurePositionXInertiaModifiers(modifiers); |
| 1212 | + } else { |
| 1213 | + m_interactionTracker.ConfigurePositionYInertiaModifiers(modifiers); |
| 1214 | + } |
| 1215 | + } else { |
| 1216 | + // Clear inertia modifiers when no snapping is configured |
| 1217 | + if (m_horizontal) { |
| 1218 | + m_interactionTracker.ConfigurePositionXInertiaModifiers({}); |
| 1219 | + } else { |
| 1220 | + m_interactionTracker.ConfigurePositionYInertiaModifiers({}); |
| 1221 | + } |
| 1222 | + } |
| 1223 | + } |
| 1224 | + |
1053 | 1225 | bool m_isScrollEnabled{true};
|
1054 | 1226 | bool m_horizontal{false};
|
| 1227 | + bool m_snapToStart{true}; |
| 1228 | + bool m_snapToEnd{true}; |
| 1229 | + std::vector<float> m_snapToOffsets; |
1055 | 1230 | bool m_inertia{false};
|
1056 | 1231 | bool m_custom{false};
|
1057 | 1232 | winrt::Windows::Foundation::Numerics::float3 m_targetPosition;
|
|
0 commit comments