|  | 
| 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