diff --git a/FeatureAreas.props b/FeatureAreas.props
old mode 100644
new mode 100755
index 87d2acb705..e0e151b676
--- a/FeatureAreas.props
+++ b/FeatureAreas.props
@@ -210,6 +210,10 @@
+
+
+ productOnly
+
@@ -271,6 +275,7 @@
true
true
true
+ true
true
diff --git a/MUXControls.sln b/MUXControls.sln
old mode 100644
new mode 100755
index e5226c5942..7e2c3740d4
--- a/MUXControls.sln
+++ b/MUXControls.sln
@@ -681,6 +681,16 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "InfoBar_TestUI", "dev\InfoB
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "InfoBar_InteractionTests", "dev\InfoBar\InteractionTests\InfoBar_InteractionTests.shproj", "{F470A64E-780E-45AA-ABB7-73A8734E51D7}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PipsPager", "PipsPager", "{CE0523BB-5799-4BA0-A461-0ABC6E19F969}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PipsPager", "dev\PipsPager\PipsPager.vcxitems", "{D1EB61D8-C689-4AD1-BD61-FDAA50362563}"
+EndProject
+Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PipsPager_APITests", "dev\PipsPager\APITests\PipsPager_APITests.shproj", "{9CF0D73A-E435-4C17-A41C-11E9FA3EEA2F}"
+EndProject
+Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PipsPager_TestUI", "dev\PipsPager\TestUI\PipsPager_TestUI.shproj", "{44F0E6BC-6222-4F16-8050-BB31DD804C4A}"
+EndProject
+Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PipsPager_InteractionTests", "dev\PipsPager\InteractionTests\PipsPager_InteractionTests.shproj", "{B1D8E6A2-3FE6-4D80-9685-26DF2C9F4331}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ImageIcon", "ImageIcon", "{BB791907-485F-4A16-9612-7FE07FCD1D21}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImageIcon", "dev\ImageIcon\ImageIcon.vcxitems", "{9FB38577-696E-47BA-8AE2-F48A3C84A7CA}"
@@ -750,6 +760,7 @@ Global
dev\TeachingTip\TestUI\TeachingTip_TestUI.projitems*{42a51d3e-f06a-41a0-be4c-f94cddb80678}*SharedItemsImports = 13
dev\RadioButtons\InteractionTests\RadioButtons_InteractionTests.projitems*{42d6e8f9-59fe-4ca5-83eb-69a7622f5742}*SharedItemsImports = 13
dev\TwoPaneView\APITests\TwoPaneView_APITests.projitems*{44deafbc-bb7a-4b02-aeab-29df2c2f8587}*SharedItemsImports = 13
+ dev\PipsPager\TestUI\PipsPager_TestUI.projitems*{44f0e6bc-6222-4f16-8050-bb31dd804c4a}*SharedItemsImports = 13
dev\ResourceHelper\ResourceHelper.vcxitems*{45d41acc-2c3c-43d2-bc10-02aa73ffc7c7}*SharedItemsImports = 9
dev\ScrollPresenter\APITests\ScrollPresenter_APITests.projitems*{474b92f7-cd58-fed9-8569-9640529d1871}*SharedItemsImports = 13
dev\NavigationView\NavigationView_InteractionTests\NavigationView_InteractionTests.projitems*{475c3a33-637a-44dc-b789-6c2d78a75283}*SharedItemsImports = 13
@@ -808,6 +819,7 @@ Global
dev\Repeater\InteractionTests\Repeater_InteractionTests.projitems*{999e00c9-0e58-402a-8e0e-cbafb0adc7e3}*SharedItemsImports = 13
dev\SwipeControl\SwipeControl_InteractionTests\SwipeControl_InteractionTests.projitems*{9a8da438-193c-4950-a046-2952de2d3b0b}*SharedItemsImports = 13
dev\TwoPaneView\TestUI\TwoPaneView_TestUI.projitems*{9c533ec3-f8fa-4b0e-ba1b-3323932cdfcb}*SharedItemsImports = 13
+ dev\PipsPager\APITests\PipsPager_APITests.projitems*{9cf0d73a-e435-4c17-a41c-11e9fa3eea2f}*SharedItemsImports = 13
dev\NumberBox\NumberBox.vcxitems*{9d23c997-1f46-444a-8c07-4a4bff7e4e63}*SharedItemsImports = 9
dev\ImageIcon\ImageIcon.vcxitems*{9fb38577-696e-47ba-8ae2-f48a3c84a7ca}*SharedItemsImports = 9
dev\Repeater\Repeater.vcxitems*{a0aa8919-2140-42db-beb1-b2c3ace594f4}*SharedItemsImports = 9
@@ -850,6 +862,7 @@ Global
dev\PagerControl\PagerControl.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
dev\ParallaxView\ParallaxView.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
dev\PersonPicture\PersonPicture.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
+ dev\PipsPager\PipsPager.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
dev\Pivot\Pivot.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
dev\ProgressBar\ProgressBar.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
dev\ProgressRing\ProgressRing.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
@@ -884,6 +897,7 @@ Global
dev\TimePicker\TimePicker.vcxitems*{afd20f66-d203-4da9-b8ea-4e3f99a53f99}*SharedItemsImports = 9
dev\CalendarView\CalendarView.vcxitems*{b0016539-9ee1-42b4-a247-fd45c8511656}*SharedItemsImports = 9
dev\PersonPicture\InteractionTests\PersonPicture_InteractionTests.projitems*{b0c15318-1f57-4914-b860-ebf248841511}*SharedItemsImports = 13
+ dev\PipsPager\InteractionTests\PipsPager_InteractionTests.projitems*{b1d8e6a2-3fe6-4d80-9685-26df2c9f4331}*SharedItemsImports = 13
dev\TreeView\TestUI\TreeView_TestUI.projitems*{b2c714dd-9c6b-400c-9cef-13a2d48378bd}*SharedItemsImports = 13
dev\AnimatedVisualPlayer\AnimatedVisualPlayer.vcxitems*{b39300d2-4510-44ea-aa7b-eda9118f830e}*SharedItemsImports = 9
dev\ProgressRing\TestUI\ProgressRing_TestUI.projitems*{b58ec806-9951-4e5e-af29-a700a088770e}*SharedItemsImports = 13
@@ -908,6 +922,7 @@ Global
dev\LayoutPanel\APITests\LayoutPanel_APITests.projitems*{cddf46ef-aa2d-4bb3-b33e-98b3dbb3c41b}*SharedItemsImports = 13
dev\Interactions\SliderInteraction\SliderInteraction.vcxitems*{d097a4d5-6b61-424d-99f0-f335eff41665}*SharedItemsImports = 9
dev\TabView\InteractionTests\TabView_InteractionTests.projitems*{d1e297b4-5e5b-4807-8624-4141c817a98a}*SharedItemsImports = 13
+ dev\PipsPager\PipsPager.vcxitems*{d1eb61d8-c689-4ad1-bd61-fdaa50362563}*SharedItemsImports = 9
dev\MenuFlyout\MenuFlyout.vcxitems*{d5c2b2a0-50af-4ace-939d-17d1ed79fd6f}*SharedItemsImports = 9
dev\Expander\InteractionTests\Expander_InteractionTests.projitems*{d6df4ab9-facc-4e51-8c57-6b1f96919365}*SharedItemsImports = 13
dev\IconSource\APITests\IconSource_APITests.projitems*{d73627e9-564c-4a72-a12d-f6c82f17ad0d}*SharedItemsImports = 13
@@ -962,6 +977,8 @@ Global
dev\ParallaxView\TestUI\ParallaxView_TestUI.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4
dev\PersonPicture\APITests\PersonPicture_APITests.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4
dev\PersonPicture\TestUI\PersonPicture_TestUI.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4
+ dev\PipsPager\APITests\PipsPager_APITests.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4
+ dev\PipsPager\TestUI\PipsPager_TestUI.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4
dev\Pivot\TestUI\Pivot_TestUI.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4
dev\ProgressBar\TestUI\ProgressBar_TestUI.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4
dev\ProgressRing\TestUI\ProgressRing_TestUI.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4
@@ -1062,6 +1079,8 @@ Global
dev\ParallaxView\TestUI\ParallaxView_TestUI.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4
dev\PersonPicture\APITests\PersonPicture_APITests.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4
dev\PersonPicture\TestUI\PersonPicture_TestUI.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4
+ dev\PipsPager\APITests\PipsPager_APITests.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4
+ dev\PipsPager\TestUI\PipsPager_TestUI.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4
dev\Pivot\TestUI\Pivot_TestUI.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4
dev\ProgressBar\TestUI\ProgressBar_TestUI.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4
dev\ProgressRing\TestUI\ProgressRing_TestUI.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4
@@ -1733,6 +1752,11 @@ Global
{CCC102B7-F5EF-479D-94F1-008D189448B1} = {CEFD707F-6686-4CF4-8D4C-B5FECD50D739}
{32DFAF1E-C2EC-4C52-A4D8-B3A3946242B4} = {CEFD707F-6686-4CF4-8D4C-B5FECD50D739}
{F470A64E-780E-45AA-ABB7-73A8734E51D7} = {CEFD707F-6686-4CF4-8D4C-B5FECD50D739}
+ {CE0523BB-5799-4BA0-A461-0ABC6E19F969} = {67599AD5-51EC-44CB-85CE-B60CD8CBA270}
+ {D1EB61D8-C689-4AD1-BD61-FDAA50362563} = {CE0523BB-5799-4BA0-A461-0ABC6E19F969}
+ {9CF0D73A-E435-4C17-A41C-11E9FA3EEA2F} = {CE0523BB-5799-4BA0-A461-0ABC6E19F969}
+ {44F0E6BC-6222-4F16-8050-BB31DD804C4A} = {CE0523BB-5799-4BA0-A461-0ABC6E19F969}
+ {B1D8E6A2-3FE6-4D80-9685-26DF2C9F4331} = {CE0523BB-5799-4BA0-A461-0ABC6E19F969}
{BB791907-485F-4A16-9612-7FE07FCD1D21} = {67599AD5-51EC-44CB-85CE-B60CD8CBA270}
{9FB38577-696E-47BA-8AE2-F48A3C84A7CA} = {BB791907-485F-4A16-9612-7FE07FCD1D21}
{27AAE2E5-9687-4120-822F-CDB68B9A65B7} = {BB791907-485F-4A16-9612-7FE07FCD1D21}
diff --git a/MUXControlsInnerLoop.sln b/MUXControlsInnerLoop.sln
old mode 100644
new mode 100755
index 225d5ff516..3ffd728833
--- a/MUXControlsInnerLoop.sln
+++ b/MUXControlsInnerLoop.sln
@@ -496,6 +496,16 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "InfoBar_InteractionTests",
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "InfoBar_TestUI", "dev\InfoBar\TestUI\InfoBar_TestUI.shproj", "{32DFAF1E-C2EC-4C52-A4D8-B3A3946242B4}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PipsPager", "PipsPager", "{AA960C72-877F-4F3C-92D2-7ADD34D643F4}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PipsPager", "dev\PipsPager\PipsPager.vcxitems", "{D1EB61D8-C689-4AD1-BD61-FDAA50362563}"
+EndProject
+Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PipsPager_APITests", "dev\PipsPager\APITests\PipsPager_APITests.shproj", "{9CF0D73A-E435-4C17-A41C-11E9FA3EEA2F}"
+EndProject
+Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PipsPager_TestUI", "dev\PipsPager\TestUI\PipsPager_TestUI.shproj", "{44F0E6BC-6222-4F16-8050-BB31DD804C4A}"
+EndProject
+Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PipsPager_InteractionTests", "dev\PipsPager\InteractionTests\PipsPager_InteractionTests.shproj", "{B1D8E6A2-3FE6-4D80-9685-26DF2C9F4331}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ImageIcon", "ImageIcon", "{21638B33-D4DE-4D10-84F8-3E2DACF975C7}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImageIcon", "dev\ImageIcon\ImageIcon.vcxitems", "{9FB38577-696E-47BA-8AE2-F48A3C84A7CA}"
@@ -562,6 +572,7 @@ Global
dev\TeachingTip\TestUI\TeachingTip_TestUI.projitems*{42a51d3e-f06a-41a0-be4c-f94cddb80678}*SharedItemsImports = 13
dev\RadioButtons\InteractionTests\RadioButtons_InteractionTests.projitems*{42d6e8f9-59fe-4ca5-83eb-69a7622f5742}*SharedItemsImports = 13
dev\TwoPaneView\APITests\TwoPaneView_APITests.projitems*{44deafbc-bb7a-4b02-aeab-29df2c2f8587}*SharedItemsImports = 13
+ dev\PipsPager\TestUI\PipsPager_TestUI.projitems*{44f0e6bc-6222-4f16-8050-bb31dd804c4a}*SharedItemsImports = 13
dev\ResourceHelper\ResourceHelper.vcxitems*{45d41acc-2c3c-43d2-bc10-02aa73ffc7c7}*SharedItemsImports = 9
dev\ScrollPresenter\APITests\ScrollPresenter_APITests.projitems*{474b92f7-cd58-fed9-8569-9640529d1871}*SharedItemsImports = 13
dev\NavigationView\NavigationView_InteractionTests\NavigationView_InteractionTests.projitems*{475c3a33-637a-44dc-b789-6c2d78a75283}*SharedItemsImports = 13
@@ -617,6 +628,7 @@ Global
dev\Repeater\InteractionTests\Repeater_InteractionTests.projitems*{999e00c9-0e58-402a-8e0e-cbafb0adc7e3}*SharedItemsImports = 13
dev\SwipeControl\SwipeControl_InteractionTests\SwipeControl_InteractionTests.projitems*{9a8da438-193c-4950-a046-2952de2d3b0b}*SharedItemsImports = 13
dev\TwoPaneView\TestUI\TwoPaneView_TestUI.projitems*{9c533ec3-f8fa-4b0e-ba1b-3323932cdfcb}*SharedItemsImports = 13
+ dev\PipsPager\APITests\PipsPager_APITests.projitems*{9cf0d73a-e435-4c17-a41c-11e9fa3eea2f}*SharedItemsImports = 13
dev\NumberBox\NumberBox.vcxitems*{9d23c997-1f46-444a-8c07-4a4bff7e4e63}*SharedItemsImports = 9
dev\ImageIcon\ImageIcon.vcxitems*{9fb38577-696e-47ba-8ae2-f48a3c84a7ca}*SharedItemsImports = 9
dev\Repeater\Repeater.vcxitems*{a0aa8919-2140-42db-beb1-b2c3ace594f4}*SharedItemsImports = 9
@@ -626,6 +638,7 @@ Global
dev\Materials\Acrylic\TestUI\AcrylicBrush_TestUI.projitems*{a800e818-7212-4fd7-ae3a-1dcab539db87}*SharedItemsImports = 13
dev\PagerControl\PagerControl.vcxitems*{ab3261a7-9a8d-4a27-aea2-3aac0419c889}*SharedItemsImports = 9
dev\Collections\Collections.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
+ dev\ComboBox\ComboBox.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
dev\CommonStyles\CommonStyles.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
dev\Common\Common.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
dev\DropDownButton\DropDownButton.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
@@ -634,6 +647,10 @@ Global
dev\Lights\Lights.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
dev\Materials\Acrylic\AcrylicBrush.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
dev\Materials\Reveal\RevealBrush.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
+ dev\NumberBox\NumberBox.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
+ dev\PagerControl\PagerControl.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
+ dev\PipsPager\PipsPager.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
+ dev\Repeater\Repeater.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
dev\ResourceHelper\ResourceHelper.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
dev\SplitButton\SplitButton.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
dev\Telemetry\Telemetry.vcxitems*{ad0c90b0-4845-4d4b-88f1-86f653f8171b}*SharedItemsImports = 4
@@ -643,6 +660,7 @@ Global
dev\RatingControl\InteractionTests\RatingControl_InteractionTests.projitems*{afaad014-132c-4d2a-a28e-4ef717d3e647}*SharedItemsImports = 13
dev\TimePicker\TimePicker.vcxitems*{afd20f66-d203-4da9-b8ea-4e3f99a53f99}*SharedItemsImports = 9
dev\PersonPicture\InteractionTests\PersonPicture_InteractionTests.projitems*{b0c15318-1f57-4914-b860-ebf248841511}*SharedItemsImports = 13
+ dev\PipsPager\InteractionTests\PipsPager_InteractionTests.projitems*{b1d8e6a2-3fe6-4d80-9685-26df2c9f4331}*SharedItemsImports = 13
dev\TreeView\TestUI\TreeView_TestUI.projitems*{b2c714dd-9c6b-400c-9cef-13a2d48378bd}*SharedItemsImports = 13
dev\AnimatedVisualPlayer\AnimatedVisualPlayer.vcxitems*{b39300d2-4510-44ea-aa7b-eda9118f830e}*SharedItemsImports = 9
dev\ProgressRing\TestUI\ProgressRing_TestUI.projitems*{b58ec806-9951-4e5e-af29-a700a088770e}*SharedItemsImports = 13
@@ -666,6 +684,7 @@ Global
dev\LayoutPanel\APITests\LayoutPanel_APITests.projitems*{cddf46ef-aa2d-4bb3-b33e-98b3dbb3c41b}*SharedItemsImports = 13
dev\Interactions\SliderInteraction\SliderInteraction.vcxitems*{d097a4d5-6b61-424d-99f0-f335eff41665}*SharedItemsImports = 9
dev\TabView\InteractionTests\TabView_InteractionTests.projitems*{d1e297b4-5e5b-4807-8624-4141c817a98a}*SharedItemsImports = 13
+ dev\PipsPager\PipsPager.vcxitems*{d1eb61d8-c689-4ad1-bd61-fdaa50362563}*SharedItemsImports = 9
dev\MenuFlyout\MenuFlyout.vcxitems*{d5c2b2a0-50af-4ace-939d-17d1ed79fd6f}*SharedItemsImports = 9
dev\Expander\InteractionTests\Expander_InteractionTests.projitems*{d6df4ab9-facc-4e51-8c57-6b1f96919365}*SharedItemsImports = 13
dev\IconSource\APITests\IconSource_APITests.projitems*{d73627e9-564c-4a72-a12d-f6c82f17ad0d}*SharedItemsImports = 13
@@ -676,6 +695,10 @@ Global
dev\TreeView\APITests\TreeView_APITests.projitems*{de885c66-929c-464e-bac4-3e076ec46483}*SharedItemsImports = 13
dev\Pivot\TestUI\Pivot_TestUI.projitems*{deb3fa60-e4a7-4735-89f2-363c7c56b428}*SharedItemsImports = 13
dev\CommonManaged\CommonManaged.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4
+ dev\PagerControl\APITests\PagerControl_APITests.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4
+ dev\PagerControl\TestUI\PagerControl_TestUI.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4
+ dev\PipsPager\APITests\PipsPager_APITests.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4
+ dev\PipsPager\TestUI\PipsPager_TestUI.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4
test\TestAppUtils\TestAppUtils.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4
dev\SplitButton\InteractionTests\SplitButton_InteractionTests.projitems*{e1c861e2-c4d9-41e1-aed7-5e203451bd4d}*SharedItemsImports = 13
dev\DatePicker\TestUI\DatePicker_TestUI.projitems*{e20f725c-3a53-463b-ada9-ff2088aaca4d}*SharedItemsImports = 13
@@ -694,6 +717,10 @@ Global
dev\Materials\Acrylic\InteractionTests\AcrylicBrush_InteractionTests.projitems*{f601284a-00c1-49f9-99b3-70d45585f784}*SharedItemsImports = 13
dev\SplitButton\SplitButton.vcxitems*{faf114dd-af1f-4d9f-a511-354c19912aad}*SharedItemsImports = 9
dev\CommonManaged\CommonManaged.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4
+ dev\PagerControl\APITests\PagerControl_APITests.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4
+ dev\PagerControl\TestUI\PagerControl_TestUI.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4
+ dev\PipsPager\APITests\PipsPager_APITests.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4
+ dev\PipsPager\TestUI\PipsPager_TestUI.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4
test\TestAppUtils\TestAppUtils.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4
dev\Slider\Slider.vcxitems*{fc2178ca-7f72-40f0-916c-a2b3750bbb6c}*SharedItemsImports = 9
dev\LayoutPanel\LayoutPanel.vcxitems*{fd3c1a00-0d07-4849-a3b9-646f0ff21d7b}*SharedItemsImports = 9
@@ -1154,6 +1181,11 @@ Global
{CCC102B7-F5EF-479D-94F1-008D189448B1} = {1AD0CB4F-47F0-432B-8D4F-CE33FA3EB8A9}
{F470A64E-780E-45AA-ABB7-73A8734E51D7} = {1AD0CB4F-47F0-432B-8D4F-CE33FA3EB8A9}
{32DFAF1E-C2EC-4C52-A4D8-B3A3946242B4} = {1AD0CB4F-47F0-432B-8D4F-CE33FA3EB8A9}
+ {AA960C72-877F-4F3C-92D2-7ADD34D643F4} = {67599AD5-51EC-44CB-85CE-B60CD8CBA270}
+ {D1EB61D8-C689-4AD1-BD61-FDAA50362563} = {AA960C72-877F-4F3C-92D2-7ADD34D643F4}
+ {9CF0D73A-E435-4C17-A41C-11E9FA3EEA2F} = {AA960C72-877F-4F3C-92D2-7ADD34D643F4}
+ {44F0E6BC-6222-4F16-8050-BB31DD804C4A} = {AA960C72-877F-4F3C-92D2-7ADD34D643F4}
+ {B1D8E6A2-3FE6-4D80-9685-26DF2C9F4331} = {AA960C72-877F-4F3C-92D2-7ADD34D643F4}
{21638B33-D4DE-4D10-84F8-3E2DACF975C7} = {67599AD5-51EC-44CB-85CE-B60CD8CBA270}
{9FB38577-696E-47BA-8AE2-F48A3C84A7CA} = {21638B33-D4DE-4D10-84F8-3E2DACF975C7}
{27AAE2E5-9687-4120-822F-CDB68B9A65B7} = {21638B33-D4DE-4D10-84F8-3E2DACF975C7}
diff --git a/build/Localization/Settings/LocConfig.xml b/build/Localization/Settings/LocConfig.xml
index 40da925f22..c677ba2a16 100644
--- a/build/Localization/Settings/LocConfig.xml
+++ b/build/Localization/Settings/LocConfig.xml
@@ -45,5 +45,8 @@
+
diff --git a/dev/PipsPager/APITests/PipsPagerTests.cs b/dev/PipsPager/APITests/PipsPagerTests.cs
new file mode 100755
index 0000000000..b00280d093
--- /dev/null
+++ b/dev/PipsPager/APITests/PipsPagerTests.cs
@@ -0,0 +1,136 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using Common;
+using Microsoft.UI.Xaml.Controls;
+using MUXControlsTestApp.Utilities;
+using Microsoft.UI.Xaml.Automation.Peers;
+using Windows.UI.Xaml.Automation.Provider;
+using Windows.UI.Xaml.Automation.Peers;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Automation;
+#if USING_TAEF
+using WEX.TestExecution;
+using WEX.TestExecution.Markup;
+using WEX.Logging.Interop;
+#else
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.VisualStudio.TestTools.UnitTesting.Logging;
+#endif
+
+namespace Windows.UI.Xaml.Tests.MUXControls.ApiTests
+{
+ [TestClass]
+ public class PipsPagerTests : ApiTestBase
+ {
+ [TestMethod]
+ public void VerifyAutomationPeerBehavior()
+ {
+ RunOnUIThread.Execute(() =>
+ {
+ var pipsControl = new PipsPager();
+ pipsControl.NumberOfPages = 5;
+ Content = pipsControl;
+
+ var peer = PipsPagerAutomationPeer.CreatePeerForElement(pipsControl);
+ var selectionPeer = peer as ISelectionProvider;
+ Verify.AreEqual(false, selectionPeer.CanSelectMultiple);
+ Verify.AreEqual(true, selectionPeer.IsSelectionRequired);
+ Verify.AreEqual(AutomationLandmarkType.Navigation, peer.GetLandmarkType());
+ });
+ }
+
+ [TestMethod]
+ public void VerifyPipsPagerButtonUIABehavior()
+ {
+ RunOnUIThread.Execute(() =>
+ {
+ var pipsPager = new PipsPager();
+ pipsPager.NumberOfPages = 5;
+ Content = pipsPager;
+ });
+
+ IdleSynchronizer.Wait();
+
+ RunOnUIThread.Execute(() =>
+ {
+ var rootPanel = VisualTreeHelper.GetChild(Content, 0) as StackPanel;
+ var repeaterRootParent = VisualTreeHelper.GetChild(rootPanel, 1);
+ ItemsRepeater repeater = null;
+ while (repeater == null)
+ {
+ var nextChild = VisualTreeHelper.GetChild(repeaterRootParent, 0);
+ repeater = nextChild as ItemsRepeater;
+ repeaterRootParent = nextChild;
+ }
+ for (int i = 0; i < 5; i++)
+ {
+ var button = repeater.TryGetElement(i);
+ Verify.IsNotNull(button);
+ Verify.AreEqual(i + 1, button.GetValue(AutomationProperties.PositionInSetProperty));
+ Verify.AreEqual(5, button.GetValue(AutomationProperties.SizeOfSetProperty));
+ }
+ });
+ }
+
+ [TestMethod]
+ public void VerifyEmptyPagerDoesNotCrash()
+ {
+ RunOnUIThread.Execute(() =>
+ {
+ Content = new PipsPager();
+ });
+
+ IdleSynchronizer.Wait();
+
+ RunOnUIThread.Execute(() =>
+ {
+ Verify.IsNotNull(Content);
+ });
+ }
+
+ [TestMethod]
+ public void VerifySelectedIndexChangedEventArgs()
+ {
+ PipsPager pager = null;
+ var previousIndex = -2;
+ var newIndex = -2;
+ RunOnUIThread.Execute(() =>
+ {
+ pager = new PipsPager();
+ pager.SelectedIndexChanged += Pager_SelectedIndexChanged;
+ Content = pager;
+
+ });
+
+ IdleSynchronizer.Wait();
+
+ RunOnUIThread.Execute(() =>
+ {
+ VerifySelectionChanged(-1, 0);
+
+ pager.NumberOfPages = 10;
+ VerifySelectionChanged(-1, 0);
+
+ pager.SelectedPageIndex = 9;
+ VerifySelectionChanged(0, 9);
+
+ pager.SelectedPageIndex = 4;
+ VerifySelectionChanged(9, 4);
+ });
+
+ void Pager_SelectedIndexChanged(PipsPager sender, PipsPagerSelectedIndexChangedEventArgs args)
+ {
+ previousIndex = args.PreviousPageIndex;
+ newIndex = args.NewPageIndex;
+ }
+
+ void VerifySelectionChanged(int expectedPreviousIndex, int expectedNewIndex)
+ {
+ Verify.AreEqual(expectedPreviousIndex, previousIndex, "Expected PreviousPageIndex:" + expectedPreviousIndex + ", actual: " + previousIndex);
+ Verify.AreEqual(expectedNewIndex, newIndex, "Expected PreviousPageIndex:" + expectedNewIndex + ", actual: " + newIndex);
+ }
+ }
+ }
+}
diff --git a/dev/PipsPager/APITests/PipsPager_APITests.projitems b/dev/PipsPager/APITests/PipsPager_APITests.projitems
new file mode 100644
index 0000000000..1d24bd44f2
--- /dev/null
+++ b/dev/PipsPager/APITests/PipsPager_APITests.projitems
@@ -0,0 +1,14 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ cb2352e2-d633-41a3-8cdc-b28731a4c490
+
+
+ PipsPager_APITests
+
+
+
+
+
diff --git a/dev/PipsPager/APITests/PipsPager_APITests.shproj b/dev/PipsPager/APITests/PipsPager_APITests.shproj
new file mode 100755
index 0000000000..854425119b
--- /dev/null
+++ b/dev/PipsPager/APITests/PipsPager_APITests.shproj
@@ -0,0 +1,13 @@
+
+
+
+ {9CF0D73A-E435-4C17-A41C-11E9FA3EEA2F}
+ 14.0
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dev/PipsPager/InteractionTests/PipsPagerElements.cs b/dev/PipsPager/InteractionTests/PipsPagerElements.cs
new file mode 100755
index 0000000000..2b18bb935c
--- /dev/null
+++ b/dev/PipsPager/InteractionTests/PipsPagerElements.cs
@@ -0,0 +1,150 @@
+using Windows.UI.Xaml.Tests.MUXControls.InteractionTests.Common;
+using Common;
+using Microsoft.Windows.Apps.Test.Foundation;
+using Microsoft.Windows.Apps.Test.Foundation.Controls;
+
+#if USING_TAEF
+using WEX.TestExecution;
+using WEX.TestExecution.Markup;
+using WEX.Logging.Interop;
+#else
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.VisualStudio.TestTools.UnitTesting.Logging;
+#endif
+
+namespace Windows.UI.Xaml.Tests.MUXControls.InteractionTests
+{
+ public class PipsPagerElements
+ {
+ private UIObject PipsPager;
+ private UIObject NextPageButton;
+ private UIObject PreviousPageButton;
+ private ComboBox PreviousPageButtonVisibilityComboBox;
+ private ComboBox NextPageButtonVisibilityComboBox;
+ private ComboBox NumberOfPagesComboBox;
+ private ComboBox MaxVisualIndicatorsComboBox;
+ private ComboBox OrientationComboBox;
+ private CheckBox PreviousPageButtonIsVisibleCheckBox;
+ private CheckBox PreviousPageButtonIsEnabledCheckBox;
+ private CheckBox NextPageButtonIsVisibleCheckBox;
+ private CheckBox NextPageButtonIsEnabledCheckBox;
+ private TextBlock CurrentPageTextBlock;
+ private TextBlock CurrentNumberOfPagesTextBlock;
+ private TextBlock CurrentMaxVisualIndicatorsTextBlock;
+ private TextBlock CurrentOrientationTextBlock;
+
+ public UIObject GetPipsPager()
+ {
+ return GetElement(ref PipsPager, "TestPipsPager");
+ }
+ public UIObject GetPreviousPageButton()
+ {
+ return GetElementWithinPager(ref PreviousPageButton, "PreviousPageButton");
+ }
+
+ public UIObject GetNextPageButton()
+ {
+ return GetElementWithinPager(ref NextPageButton, "NextPageButton");
+ }
+
+ public ComboBox GetPreviousPageButtonVisibilityComboBox()
+ {
+ return GetElement(ref PreviousPageButtonVisibilityComboBox, "PreviousPageButtonVisibilityComboBox");
+ }
+
+ public ComboBox GetNextPageButtonVisibilityComboBox()
+ {
+ return GetElement(ref NextPageButtonVisibilityComboBox, "NextPageButtonVisibilityComboBox");
+ }
+
+ public CheckBox GetPreviousPageButtonIsVisibleCheckBox()
+ {
+ return GetElement(ref PreviousPageButtonIsVisibleCheckBox, "PreviousPageButtonIsVisibleCheckBox");
+ }
+ public CheckBox GetPreviousPageButtonIsEnabledCheckBox()
+ {
+ return GetElement(ref PreviousPageButtonIsEnabledCheckBox, "PreviousPageButtonIsEnabledCheckBox");
+ }
+ public CheckBox GetNextPageButtonIsVisibleCheckBox()
+ {
+ return GetElement(ref NextPageButtonIsVisibleCheckBox, "NextPageButtonIsVisibleCheckBox");
+ }
+ public CheckBox GetNextPageButtonIsEnabledCheckBox()
+ {
+ return GetElement(ref NextPageButtonIsEnabledCheckBox, "NextPageButtonIsEnabledCheckBox");
+ }
+
+ public ComboBox GetNumberOfPagesComboBox()
+ {
+ return GetElement(ref NumberOfPagesComboBox, "TestPipsPagerNumberOfPagesComboBox");
+ }
+
+ public ComboBox GetMaxVisualIndicatorsComboBox()
+ {
+ return GetElement(ref MaxVisualIndicatorsComboBox, "TestPipsPagerMaxVisualIndicatorsComboBox");
+ }
+ public ComboBox GetOrientationComboBox()
+ {
+ return GetElement(ref OrientationComboBox, "TestPipsPagerOrientationComboBox");
+ }
+ public TextBlock GetCurrentPageTextBlock()
+ {
+ return GetElement(ref CurrentPageTextBlock, "CurrentPageIndexTextBlock");
+ }
+
+ public TextBlock GetCurrentMaxVisualIndicatorsTextBlock()
+ {
+ return GetElement(ref CurrentMaxVisualIndicatorsTextBlock, "CurrentMaxVisualIndicatorsTextBlock");
+ }
+
+ public TextBlock GetCurrentNumberOfPagesTextBlock()
+ {
+ return GetElement(ref CurrentNumberOfPagesTextBlock, "CurrentNumberOfPagesTextBlock");
+ }
+
+ public TextBlock GetCurrentOrientationTextBlock()
+ {
+ return GetElement(ref CurrentOrientationTextBlock, "CurrentOrientationTextBlock");
+ }
+
+ public UIObject GetPipWithPageNumber(string elementName)
+ {
+ foreach (var element in GetPipsPager().Children)
+ {
+ if (element.Name == elementName)
+ {
+ return element;
+ }
+ }
+ return null;
+ }
+ private T GetElement(ref T element, string elementName) where T : UIObject
+ {
+ if (element == null)
+ {
+ Log.Comment("Find the " + elementName);
+ element = FindElement.ByNameOrId(elementName);
+ Verify.IsNotNull(element);
+ }
+ return element;
+ }
+ private T GetElementWithinPager(ref T element, string elementName) where T : UIObject
+ {
+ if (element == null)
+ {
+ Log.Comment("Find the " + elementName);
+
+ foreach (T child in GetPipsPager().Children)
+ {
+ if (child.AutomationId == elementName)
+ {
+ element = child;
+ break;
+ }
+ }
+ Verify.IsNotNull(element);
+ }
+ return element;
+ }
+ }
+}
diff --git a/dev/PipsPager/InteractionTests/PipsPagerTestBase.cs b/dev/PipsPager/InteractionTests/PipsPagerTestBase.cs
new file mode 100755
index 0000000000..be2445073a
--- /dev/null
+++ b/dev/PipsPager/InteractionTests/PipsPagerTestBase.cs
@@ -0,0 +1,187 @@
+using Common;
+using Microsoft.Windows.Apps.Test.Automation;
+#if USING_TAEF
+using WEX.TestExecution;
+using WEX.TestExecution.Markup;
+using WEX.Logging.Interop;
+#else
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.VisualStudio.TestTools.UnitTesting.Logging;
+#endif
+using System;
+using System.Linq;
+using Windows.UI.Xaml.Tests.MUXControls.InteractionTests.Common;
+
+
+namespace Windows.UI.Xaml.Tests.MUXControls.InteractionTests
+{
+ public class PipsPagerTestBase
+ {
+ protected PipsPagerElements elements;
+ protected delegate void SetButtonVisibilityModeFunction(ButtonVisibilityMode mode);
+ protected delegate bool GetButtonIsHiddenOnEdgeFunction();
+
+ protected void SelectPageInPager(int index)
+ {
+ InputHelper.LeftClick(elements.GetPipWithPageNumber("Page " + index.ToString()));
+ Wait.ForIdle();
+ }
+
+ protected void VerifyPageButtonWithVisibilityModeSet(ButtonType btnType, ButtonVisibilityMode modeSet, bool isPagerInFocus = false)
+ {
+ ToggleState isVisibleState;
+ ToggleState isEnabledState;
+ bool isHiddenOnEdge;
+
+ if (btnType == ButtonType.Previous)
+ {
+ isVisibleState = elements.GetPreviousPageButtonIsVisibleCheckBox().ToggleState;
+ isEnabledState = elements.GetPreviousPageButtonIsEnabledCheckBox().ToggleState;
+ isHiddenOnEdge = GetCurrentPageAsInt32() == 0;
+ }
+ else
+ {
+ isVisibleState = elements.GetNextPageButtonIsVisibleCheckBox().ToggleState;
+ isEnabledState = elements.GetNextPageButtonIsEnabledCheckBox().ToggleState;
+ isHiddenOnEdge = !(GetCurrentPage() == "Infinite") && GetCurrentPageAsInt32() == GetSelectedNumberOfPagesAsInt32() - 1;
+ }
+ switch (modeSet)
+ {
+ case ButtonVisibilityMode.Collapsed:
+ Verify.AreEqual(isVisibleState, ToggleState.Off);
+ Verify.AreEqual(isEnabledState, ToggleState.Off);
+ break;
+
+ case ButtonVisibilityMode.Visible:
+ if (isHiddenOnEdge)
+ {
+ Verify.AreEqual(isVisibleState, ToggleState.Off);
+ Verify.AreEqual(isEnabledState, ToggleState.Off);
+ }
+ else
+ {
+ Verify.AreEqual(isVisibleState, ToggleState.On);
+ Verify.AreEqual(isEnabledState, ToggleState.On);
+ }
+ break;
+
+ case ButtonVisibilityMode.VisibleOnHover:
+ if (isHiddenOnEdge)
+ {
+ Verify.AreEqual(isVisibleState, ToggleState.Off);
+ Verify.AreEqual(isEnabledState, ToggleState.Off);
+ }
+ else
+ {
+ if (isPagerInFocus)
+ {
+ Verify.AreEqual(isVisibleState, ToggleState.On);
+ }
+ else
+ {
+ Verify.AreEqual(isVisibleState, ToggleState.Off);
+ }
+ Verify.AreEqual(isEnabledState, ToggleState.On);
+ }
+ break;
+ }
+ }
+
+ protected void VerifyNextPageButtonVisibility(Visibility expected)
+ {
+ Verify.AreEqual(expected == Visibility.Visible, elements.GetNextPageButtonIsVisibleCheckBox().ToggleState == ToggleState.On);
+ }
+
+ protected void SetNextPageButtonVisibilityMode(ButtonVisibilityMode mode)
+ {
+ elements.GetNextPageButtonVisibilityComboBox().SelectItemByName($"{mode}NextButton");
+ }
+
+ protected void SetPreviousPageButtonVisibilityMode(ButtonVisibilityMode mode)
+ {
+ elements.GetPreviousPageButtonVisibilityComboBox().SelectItemByName($"{mode}PreviousButton");
+ }
+
+ protected void SetOrientation(OrientationType orientation)
+ {
+ elements.GetOrientationComboBox().SelectItemByName($"{orientation}Orientation");
+ }
+
+ protected void VerifyOrientationChanged(OrientationType orientation)
+ {
+ Verify.AreEqual($"{orientation}", GetCurrentOrientation());
+ }
+
+ protected void VerifyPageChanged(int expectedPage)
+ {
+ Verify.AreEqual(expectedPage, GetCurrentPageAsInt32());
+ Log.Comment($"Changing to page {expectedPage}");
+ }
+
+ protected string GetCurrentOrientation()
+ {
+ return elements.GetCurrentOrientationTextBlock().GetText().Split(' ').Last();
+ }
+ protected int GetCurrentPageAsInt32()
+ {
+ return Convert.ToInt32(GetCurrentPage());
+ }
+ protected string GetCurrentPage()
+ {
+ string currentPageTextBlockContent = elements.GetCurrentPageTextBlock().GetText();
+ return new string(currentPageTextBlockContent.Where(char.IsDigit).ToArray());
+ }
+
+ protected void ChangeNumberOfPages(NumberOfPagesOptions numberOfPages)
+ {
+ elements.GetNumberOfPagesComboBox().SelectItemByName($"{numberOfPages}NumberOfPages");
+ }
+
+ protected string GetSelectedNumberOfPages()
+ {
+ string selectedNumberOfPages = ExtractNumberFromString(elements.GetCurrentNumberOfPagesTextBlock().GetText());
+ if (string.IsNullOrEmpty(selectedNumberOfPages))
+ {
+ selectedNumberOfPages = "Infinite";
+ }
+ return selectedNumberOfPages;
+ }
+
+ protected int GetSelectedNumberOfPagesAsInt32()
+ {
+ return Convert.ToInt32(GetSelectedNumberOfPages());
+ }
+ protected void VerifyNumberOfPages(string numberOfPages)
+ {
+ Verify.AreEqual(numberOfPages, GetSelectedNumberOfPages());
+
+ }
+
+ private string ExtractNumberFromString(string text)
+ {
+ return new string(text.Where(char.IsDigit).ToArray());
+ }
+
+ public enum ButtonType
+ {
+ Previous,
+ Next
+ }
+
+ public enum ButtonVisibilityMode
+ {
+ Visible,
+ VisibleOnHover,
+ Collapsed
+ }
+
+ public enum NumberOfPagesOptions
+ {
+ Zero,
+ Five,
+ Ten,
+ Twenty,
+ Infinite
+ }
+ }
+}
diff --git a/dev/PipsPager/InteractionTests/PipsPagerTests.cs b/dev/PipsPager/InteractionTests/PipsPagerTests.cs
new file mode 100755
index 0000000000..3a4bfb9ddf
--- /dev/null
+++ b/dev/PipsPager/InteractionTests/PipsPagerTests.cs
@@ -0,0 +1,250 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+
+using Windows.UI.Xaml.Tests.MUXControls.InteractionTests.Infra;
+using Windows.UI.Xaml.Tests.MUXControls.InteractionTests.Common;
+#if USING_TAEF
+using WEX.TestExecution;
+using WEX.TestExecution.Markup;
+using WEX.Logging.Interop;
+#else
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+#endif
+
+namespace Windows.UI.Xaml.Tests.MUXControls.InteractionTests
+{
+ [TestClass]
+ public class PipsPagerTests : PipsPagerTestBase
+ {
+ [ClassInitialize]
+ [TestProperty("RunAs", "User")]
+ [TestProperty("Classification", "Integration")]
+ [TestProperty("Platform", "Any")]
+ [TestProperty("MUXControlsTestSuite", "SuiteB")]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ TestEnvironment.Initialize(testContext);
+ }
+
+ public void TestCleanup()
+ {
+ TestCleanupHelper.Cleanup();
+ }
+
+ [TestMethod]
+ [TestProperty("TestSuite", "A")]
+ public void PipsPagerChangingPageTest()
+ {
+ using (var setup = new TestSetupHelper("PipsPager Tests"))
+ {
+ elements = new PipsPagerElements();
+ SetPreviousPageButtonVisibilityMode(ButtonVisibilityMode.Visible);
+ SetNextPageButtonVisibilityMode(ButtonVisibilityMode.Visible);
+
+ VerifyPageChanged(0);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(1);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(2);
+
+ InputHelper.LeftClick(elements.GetPreviousPageButton());
+ VerifyPageChanged(1);
+
+ InputHelper.LeftClick(elements.GetPreviousPageButton());
+ VerifyPageChanged(0);
+
+ ChangeNumberOfPages(NumberOfPagesOptions.Five);
+ VerifyNumberOfPages("5");
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(1);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(2);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(3);
+
+ InputHelper.LeftClick(elements.GetPreviousPageButton());
+ VerifyPageChanged(2);
+ }
+ }
+
+ [TestMethod]
+ [TestProperty("TestSuite", "B")]
+ public void PreviousPageButtonChangingPageTest()
+ {
+ using (var setup = new TestSetupHelper("PipsPager Tests"))
+ {
+ elements = new PipsPagerElements();
+ SetPreviousPageButtonVisibilityMode(ButtonVisibilityMode.Visible);
+ SetNextPageButtonVisibilityMode(ButtonVisibilityMode.Visible);
+
+ VerifyPageChanged(0);
+ InputHelper.LeftClick(elements.GetNextPageButton());
+
+ InputHelper.LeftClick(elements.GetPreviousPageButton());
+ VerifyPageChanged(0);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ InputHelper.LeftClick(elements.GetNextPageButton());
+
+ InputHelper.LeftClick(elements.GetPreviousPageButton());
+ VerifyPageChanged(1);
+ }
+ }
+
+ [TestMethod]
+ [TestProperty("TestSuite", "B")]
+ public void NextPageButtonChangingPageTest()
+ {
+ using (var setup = new TestSetupHelper("PipsPager Tests"))
+ {
+ elements = new PipsPagerElements();
+ SetPreviousPageButtonVisibilityMode(ButtonVisibilityMode.Visible);
+ SetNextPageButtonVisibilityMode(ButtonVisibilityMode.Visible);
+
+ VerifyPageChanged(0);
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(1);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(2);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(3);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(4);
+ }
+ }
+
+ [TestMethod]
+ [TestProperty("TestSuite", "C")]
+ public void PipsPagerInfinitePagesTest()
+ {
+ using (var setup = new TestSetupHelper("PipsPager Tests"))
+ {
+ elements = new PipsPagerElements();
+ SetPreviousPageButtonVisibilityMode(ButtonVisibilityMode.Visible);
+ SetNextPageButtonVisibilityMode(ButtonVisibilityMode.Visible);
+
+ VerifyPageChanged(0);
+
+ ChangeNumberOfPages(NumberOfPagesOptions.Infinite);
+ VerifyNumberOfPages("Infinite");
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(1);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(2);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(3);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(4);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(5);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(6);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageChanged(7);
+ }
+ }
+
+ [TestMethod]
+ [TestProperty("TestSuite", "D")]
+ public void PreviousPageButtonVisibilityOptionsTest()
+ {
+ using (var setup = new TestSetupHelper("PipsPager Tests"))
+ {
+ elements = new PipsPagerElements();
+ SetNextPageButtonVisibilityMode(ButtonVisibilityMode.Visible);
+
+ /* Test for Collapsed */
+ SetPreviousPageButtonVisibilityMode(ButtonVisibilityMode.Collapsed);
+ VerifyPageButtonWithVisibilityModeSet(ButtonType.Previous, ButtonVisibilityMode.Collapsed);
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageButtonWithVisibilityModeSet(ButtonType.Previous, ButtonVisibilityMode.Collapsed);
+
+ /* Test for Visible */
+ SetPreviousPageButtonVisibilityMode(ButtonVisibilityMode.Visible);
+ VerifyPageButtonWithVisibilityModeSet(ButtonType.Previous, ButtonVisibilityMode.Visible);
+ InputHelper.LeftClick(elements.GetPreviousPageButton());
+ VerifyPageButtonWithVisibilityModeSet(ButtonType.Previous, ButtonVisibilityMode.Collapsed);
+
+ /* Test for VisibleOnHover */
+ SetPreviousPageButtonVisibilityMode(ButtonVisibilityMode.VisibleOnHover);
+ VerifyPageButtonWithVisibilityModeSet(ButtonType.Previous, ButtonVisibilityMode.Collapsed);
+
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageButtonWithVisibilityModeSet(ButtonType.Previous, ButtonVisibilityMode.VisibleOnHover, true);
+
+ InputHelper.LeftClick(elements.GetCurrentNumberOfPagesTextBlock());
+ VerifyPageButtonWithVisibilityModeSet(ButtonType.Previous, ButtonVisibilityMode.VisibleOnHover);
+ }
+ }
+
+ [TestMethod]
+ [TestProperty("TestSuite", "D")]
+ public void NextPageButtonVisibilityOptionsTest()
+ {
+ using (var setup = new TestSetupHelper("PipsPager Tests"))
+ {
+ elements = new PipsPagerElements();
+ SetPreviousPageButtonVisibilityMode(ButtonVisibilityMode.VisibleOnHover);
+
+ ChangeNumberOfPages(NumberOfPagesOptions.Five);
+ VerifyNumberOfPages("5");
+
+ /* Test for Collapsed */
+ SetNextPageButtonVisibilityMode(ButtonVisibilityMode.Collapsed);
+ VerifyPageButtonWithVisibilityModeSet(ButtonType.Next, ButtonVisibilityMode.Collapsed);
+
+ /* Test for Visible */
+ SetNextPageButtonVisibilityMode(ButtonVisibilityMode.Visible);
+ VerifyPageButtonWithVisibilityModeSet(ButtonType.Next, ButtonVisibilityMode.Visible);
+ /* We step until the end of the list (4 times, since we have 5 pages) */
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ InputHelper.LeftClick(elements.GetNextPageButton());
+ VerifyPageButtonWithVisibilityModeSet(ButtonType.Next, ButtonVisibilityMode.Collapsed);
+
+ /* Test for VisibleOnHover */
+ SetNextPageButtonVisibilityMode(ButtonVisibilityMode.VisibleOnHover);
+ VerifyPageButtonWithVisibilityModeSet(ButtonType.Next, ButtonVisibilityMode.Collapsed);
+
+ InputHelper.LeftClick(elements.GetPreviousPageButton());
+ VerifyPageButtonWithVisibilityModeSet(ButtonType.Next, ButtonVisibilityMode.VisibleOnHover, true);
+
+ InputHelper.LeftClick(elements.GetCurrentNumberOfPagesTextBlock());
+ VerifyPageButtonWithVisibilityModeSet(ButtonType.Next, ButtonVisibilityMode.VisibleOnHover);
+ }
+ }
+
+ [TestMethod]
+ [TestProperty("TestSuite", "E")]
+ public void OrientationChangeTest()
+ {
+ using (var setup = new TestSetupHelper("PipsPager Tests"))
+ {
+ elements = new PipsPagerElements();
+ SetOrientation(Microsoft.Windows.Apps.Test.Automation.OrientationType.Horizontal);
+ VerifyOrientationChanged(Microsoft.Windows.Apps.Test.Automation.OrientationType.Horizontal);
+
+ SetOrientation(Microsoft.Windows.Apps.Test.Automation.OrientationType.Vertical);
+ VerifyOrientationChanged(Microsoft.Windows.Apps.Test.Automation.OrientationType.Vertical);
+
+ }
+ }
+ }
+}
diff --git a/dev/PipsPager/InteractionTests/PipsPager_InteractionTests.projitems b/dev/PipsPager/InteractionTests/PipsPager_InteractionTests.projitems
new file mode 100755
index 0000000000..7156eb9c94
--- /dev/null
+++ b/dev/PipsPager/InteractionTests/PipsPager_InteractionTests.projitems
@@ -0,0 +1,17 @@
+
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ A1553559-5786-4B44-AB9E-94AB95C86D4D
+
+
+ PipsPager_InteractionTests
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dev/PipsPager/InteractionTests/PipsPager_InteractionTests.shproj b/dev/PipsPager/InteractionTests/PipsPager_InteractionTests.shproj
new file mode 100755
index 0000000000..4398bd0e95
--- /dev/null
+++ b/dev/PipsPager/InteractionTests/PipsPager_InteractionTests.shproj
@@ -0,0 +1,14 @@
+
+
+
+
+ {B1D8E6A2-3FE6-4D80-9685-26DF2C9F4331}
+ 15.0
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dev/PipsPager/PipsPager.cpp b/dev/PipsPager/PipsPager.cpp
new file mode 100755
index 0000000000..cc61065ca0
--- /dev/null
+++ b/dev/PipsPager/PipsPager.cpp
@@ -0,0 +1,578 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+#include "pch.h"
+#include "common.h"
+#include "Vector.h"
+#include "PipsPager.h"
+#include "RuntimeProfiler.h"
+#include "ResourceAccessor.h"
+#include "PipsPagerTemplateSettings.h"
+#include "PipsPagerSelectedIndexChangedEventArgs.h"
+#include "PipsPagerAutomationPeer.h"
+
+typedef winrt::PipsPagerButtonVisibility ButtonVisibility;
+
+static constexpr wstring_view s_pipButtonHandlersPropertyName = L"PipButtonHandlers"sv;
+
+constexpr auto c_previousPageButtonVisibleVisualState = L"PreviousPageButtonVisible"sv;
+constexpr auto c_previousPageButtonHiddenVisualState = L"PreviousPageButtonHidden"sv;
+constexpr auto c_previousPageButtonCollapsedVisualState = L"PreviousPageButtonCollapsed"sv;
+
+constexpr auto c_previousPageButtonEnabledVisualState = L"PreviousPageButtonEnabled"sv;
+constexpr auto c_previousPageButtonDisabledVisualState = L"PreviousPageButtonDisabled"sv;
+
+constexpr auto c_nextPageButtonVisibleVisualState = L"NextPageButtonVisible"sv;
+constexpr auto c_nextPageButtonHiddenVisualState = L"NextPageButtonHidden"sv;
+constexpr auto c_nextPageButtonCollapsedVisualState = L"NextPageButtonCollapsed"sv;
+
+constexpr auto c_nextPageButtonEnabledVisualState = L"NextPageButtonEnabled"sv;
+constexpr auto c_nextPageButtonDisabledVisualState = L"NextPageButtonDisabled"sv;
+
+constexpr auto c_previousPageButtonName = L"PreviousPageButton"sv;
+constexpr auto c_nextPageButtonName = L"NextPageButton"sv;
+
+constexpr auto c_pipsPagerRepeaterName = L"PipsPagerItemsRepeater"sv;
+constexpr auto c_pipsPagerScrollViewerName = L"PipsPagerScrollViewer"sv;
+
+constexpr auto c_pipsPagerButtonWidthPropertyName = L"PipsPagerButtonWidth"sv;
+constexpr auto c_pipsPagerButtonHeightPropertyName = L"PipsPagerButtonHeight"sv;
+
+constexpr auto c_pipsPagerHorizontalOrientationVisualState = L"HorizontalOrientationView"sv;
+constexpr auto c_pipsPagerVerticalOrientationVisualState = L"VerticalOrientationView"sv;
+
+PipsPager::PipsPager()
+{
+ __RP_Marker_ClassById(RuntimeProfiler::ProfId_PipsPager);
+
+ m_pipsPagerItems = winrt::make>().as>();
+ const auto templateSettings = winrt::make();
+ templateSettings.SetValue(PipsPagerTemplateSettings::s_PipsPagerItemsProperty, m_pipsPagerItems);
+ SetValue(s_TemplateSettingsProperty, templateSettings);
+
+ s_pipButtonHandlersProperty =
+ InitializeDependencyProperty(
+ s_pipButtonHandlersPropertyName,
+ winrt::name_of(),
+ winrt::name_of(),
+ true,
+ nullptr,
+ nullptr);
+ SetDefaultStyleKey(this);
+}
+
+void PipsPager::OnApplyTemplate()
+{
+ winrt::AutomationProperties::SetName(*this, ResourceAccessor::GetLocalizedStringResource(SR_PipsPagerNameText));
+
+ m_previousPageButtonClickRevoker.revoke();
+ [this](const winrt::Button button)
+ {
+ if (button)
+ {
+ winrt::AutomationProperties::SetName(button, ResourceAccessor::GetLocalizedStringResource(SR_PipsPagerPreviousPageButtonText));
+ m_previousPageButtonClickRevoker = button.Click(winrt::auto_revoke, { this, &PipsPager::OnPreviousButtonClicked });
+ }
+ }(GetTemplateChildT(c_previousPageButtonName, *this));
+
+ m_nextPageButtonClickRevoker.revoke();
+ [this](const winrt::Button button)
+ {
+ if (button)
+ {
+ winrt::AutomationProperties::SetName(button, ResourceAccessor::GetLocalizedStringResource(SR_PipsPagerNextPageButtonText));
+ m_nextPageButtonClickRevoker = button.Click(winrt::auto_revoke, { this, &PipsPager::OnNextButtonClicked });
+ }
+ }(GetTemplateChildT(c_nextPageButtonName, *this));
+
+ m_pipsPagerElementPreparedRevoker.revoke();
+ [this](const winrt::ItemsRepeater repeater)
+ {
+ m_pipsPagerRepeater.set(repeater);
+ if (repeater)
+ {
+ m_pipsPagerElementPreparedRevoker = repeater.ElementPrepared(winrt::auto_revoke, { this, &PipsPager::OnElementPrepared });
+ }
+ }(GetTemplateChildT(c_pipsPagerRepeaterName, *this));
+
+ m_pipsPagerScrollViewer.set(GetTemplateChildT(c_pipsPagerScrollViewerName, *this));
+
+ m_defaultPipSize = GetDesiredPipSize(DefaultIndicatorButtonStyle());
+ m_selectedPipSize = GetDesiredPipSize(SelectedIndicatorButtonStyle());
+ OnNavigationButtonVisibilityChanged(PreviousButtonVisibility(), c_previousPageButtonCollapsedVisualState, c_previousPageButtonDisabledVisualState);
+ OnNavigationButtonVisibilityChanged(NextButtonVisibility(), c_nextPageButtonCollapsedVisualState, c_nextPageButtonDisabledVisualState);
+ UpdatePipsItems(NumberOfPages(), MaxVisualIndicators());
+ OnOrientationChanged();
+ OnSelectedPageIndexChanged(m_lastSelectedPageIndex);
+}
+
+void PipsPager::RaiseSelectedIndexChanged()
+{
+ const auto args = winrt::make_self(m_lastSelectedPageIndex, SelectedPageIndex());
+ m_selectedIndexChangedEventSource(*this, *args);
+}
+
+winrt::Size PipsPager::GetDesiredPipSize(const winrt::Style& style) {
+ if (auto const repeater = m_pipsPagerRepeater.get())
+ {
+ if (auto const itemTemplate = repeater.ItemTemplate().try_as())
+ {
+ if (auto const element = itemTemplate.LoadContent().try_as())
+ {
+ element.Style(style);
+ element.Measure({ std::numeric_limits::infinity(), std::numeric_limits::infinity() });
+ return element.DesiredSize();
+ }
+ }
+ }
+ /* Extract default sizes and return in case the code above fails */
+ auto pipHeight = unbox_value(ResourceAccessor::ResourceLookup(*this, box_value(c_pipsPagerButtonHeightPropertyName)));
+ auto pipWidth = unbox_value(ResourceAccessor::ResourceLookup(*this, box_value(c_pipsPagerButtonWidthPropertyName)));
+ return { static_cast(pipWidth), static_cast(pipHeight) };
+}
+
+void PipsPager::OnKeyDown(const winrt::KeyRoutedEventArgs& args) {
+ winrt::FocusNavigationDirection previousPipDirection;
+ winrt::FocusNavigationDirection nextPipDirection;
+ if (Orientation() == winrt::Orientation::Vertical)
+ {
+ previousPipDirection = winrt::FocusNavigationDirection::Up;
+ nextPipDirection = winrt::FocusNavigationDirection::Down;
+ }
+ else
+ {
+ previousPipDirection = winrt::FocusNavigationDirection::Left;
+ nextPipDirection = winrt::FocusNavigationDirection::Right;
+ }
+
+ if (args.Key() == winrt::VirtualKey::Left || args.Key() == winrt::VirtualKey::Up)
+ {
+ winrt::FocusManager::TryMoveFocus(previousPipDirection);
+ args.Handled(true);
+ }
+ else if (args.Key() == winrt::VirtualKey::Right || args.Key() == winrt::VirtualKey::Down)
+ {
+ winrt::FocusManager::TryMoveFocus(nextPipDirection);
+ args.Handled(true);
+ }
+ // Call for all other presses
+ __super::OnKeyDown(args);
+}
+
+void PipsPager::OnPointerEntered(const winrt::PointerRoutedEventArgs& args) {
+ __super::OnPointerEntered(args);
+ m_isPointerOver = true;
+ UpdateNavigationButtonVisualStates();
+}
+void PipsPager::OnPointerExited(const winrt::PointerRoutedEventArgs& args) {
+ // We can get a spurious Exited and then Entered if the button
+ // that is being clicked on hides itself. In order to avoid switching
+ // visual states in this case, we check if the pointer is over the
+ // control bounds when we get the exited event.
+ if (IsOutOfControlBounds(args.GetCurrentPoint(*this).Position()))
+ {
+ m_isPointerOver = false;
+ UpdateNavigationButtonVisualStates();
+ }
+ else
+ {
+ args.Handled(true);
+ }
+ __super::OnPointerExited(args);
+}
+
+void PipsPager::OnPointerCanceled(const winrt::PointerRoutedEventArgs& args)
+{
+ __super::OnPointerCanceled(args);
+ m_isPointerOver = false;
+ UpdateNavigationButtonVisualStates();
+}
+
+bool PipsPager::IsOutOfControlBounds(const winrt::Point& point) {
+ // This is a conservative check. It is okay to say we are
+ // out of the bounds when close to the edge to account for rounding.
+ const auto tolerance = 1.0;
+ const auto actualWidth = ActualWidth();
+ const auto actualHeight = ActualHeight();
+ return point.X < tolerance ||
+ point.X > actualWidth - tolerance ||
+ point.Y < tolerance ||
+ point.Y > actualHeight - tolerance;
+}
+
+void PipsPager::UpdateIndividualNavigationButtonVisualState(
+ const bool hiddenOnEdgeCondition,
+ const ButtonVisibility visibility,
+ const wstring_view& visibleStateName,
+ const wstring_view& hiddenStateName,
+ const wstring_view& enabledStateName,
+ const wstring_view& disabledStateName) {
+
+ const auto ifGenerallyVisible = !hiddenOnEdgeCondition && NumberOfPages() != 0 && MaxVisualIndicators() > 0;
+ if (visibility != ButtonVisibility::Collapsed)
+ {
+ if ((visibility == ButtonVisibility::Visible || m_isPointerOver) && ifGenerallyVisible)
+ {
+ winrt::VisualStateManager::GoToState(*this, visibleStateName, false);
+ winrt::VisualStateManager::GoToState(*this, enabledStateName, false);
+ }
+ else
+ {
+ if (!ifGenerallyVisible)
+ {
+ winrt::VisualStateManager::GoToState(*this, disabledStateName, false);
+ }
+ else
+ {
+ winrt::VisualStateManager::GoToState(*this, enabledStateName, false);
+ }
+ winrt::VisualStateManager::GoToState(*this, hiddenStateName, false);
+ }
+ }
+}
+
+void PipsPager::UpdateNavigationButtonVisualStates() {
+ const int selectedPageIndex = SelectedPageIndex();
+ const int numberOfPages = NumberOfPages();
+
+ auto const ifPreviousButtonHiddenOnEdge = selectedPageIndex == 0;
+ UpdateIndividualNavigationButtonVisualState(ifPreviousButtonHiddenOnEdge, PreviousButtonVisibility(),
+ c_previousPageButtonVisibleVisualState, c_previousPageButtonHiddenVisualState,
+ c_previousPageButtonEnabledVisualState, c_previousPageButtonDisabledVisualState);
+
+ auto const ifNextButtonHiddenOnEdge = selectedPageIndex == numberOfPages - 1;
+ UpdateIndividualNavigationButtonVisualState(ifNextButtonHiddenOnEdge, NextButtonVisibility(),
+ c_nextPageButtonVisibleVisualState, c_nextPageButtonHiddenVisualState,
+ c_nextPageButtonEnabledVisualState, c_nextPageButtonDisabledVisualState);
+}
+
+void PipsPager::ScrollToCenterOfViewport(const winrt::UIElement sender, const int index)
+{
+ /* Vertical and Horizontal AligmentsRatio are not available until Win Version 1803 (sdk version 17134) */
+ if (SharedHelpers::IsBringIntoViewOptionsVerticalAlignmentRatioAvailable())
+ {
+ winrt::BringIntoViewOptions options;
+ options.VerticalAlignmentRatio(0.5);
+ options.HorizontalAlignmentRatio(0.5);
+ options.AnimationDesired(true);
+ sender.StartBringIntoView(options);
+ }
+ else if (const auto scrollViewer = m_pipsPagerScrollViewer.get())
+ {
+ double pipSize;
+ std::function changeViewFunc;
+ if (Orientation() == winrt::Orientation::Horizontal)
+ {
+ pipSize = m_defaultPipSize.Width;
+ changeViewFunc = [&](const double& offset) {scrollViewer.ChangeView(offset, nullptr, nullptr);};
+ }
+ else
+ {
+ pipSize = m_defaultPipSize.Height;
+ changeViewFunc = [&](const double& offset) {scrollViewer.ChangeView(nullptr, offset, nullptr);};
+ }
+ const int maxVisualIndicators = MaxVisualIndicators();
+ /* This line makes sure that while having even # of indicators the scrolling will be done correctly */
+ const int offSetChangeForEvenSizeWindow = maxVisualIndicators % 2 == 0 && index > m_lastSelectedPageIndex ? 1 : 0;
+ const int offSetNumOfElements = index + offSetChangeForEvenSizeWindow - maxVisualIndicators / 2;
+ const double offset = std::max(0.0, offSetNumOfElements * pipSize);
+ changeViewFunc(offset);
+ }
+}
+
+void PipsPager::UpdateSelectedPip(const int index) {
+ if (NumberOfPages() != 0 && MaxVisualIndicators() > 0)
+ {
+ if (const auto repeater = m_pipsPagerRepeater.get())
+ {
+ repeater.UpdateLayout();
+ if (const auto element = repeater.TryGetElement(m_lastSelectedPageIndex).try_as())
+ {
+ element.Style(DefaultIndicatorButtonStyle());
+ }
+ if (const auto element = repeater.GetOrCreateElement(index).try_as())
+ {
+ element.Style(SelectedIndicatorButtonStyle());
+ ScrollToCenterOfViewport(element, index);
+ }
+ }
+ }
+}
+
+double PipsPager::CalculateScrollViewerSize(const double defaultPipSize, const double selectedPipSize, const int numberOfPages, int maxVisualIndicators) {
+
+ auto numberOfPagesToDisplay = 0;
+ maxVisualIndicators = std::max(0, maxVisualIndicators);
+ if (maxVisualIndicators == 0 || numberOfPages == 0) {
+ return 0;
+ }
+ else if (numberOfPages > 0)
+ {
+ numberOfPagesToDisplay = std::min(maxVisualIndicators, numberOfPages);
+ }
+ else
+ {
+ numberOfPagesToDisplay = maxVisualIndicators;
+ }
+ return defaultPipSize * (numberOfPagesToDisplay - 1) + selectedPipSize;
+}
+
+void PipsPager::SetScrollViewerMaxSize() {
+ if (const auto scrollViewer = m_pipsPagerScrollViewer.get())
+ {
+ if (Orientation() == winrt::Orientation::Horizontal)
+ {
+ const auto scrollViewerWidth = CalculateScrollViewerSize(m_defaultPipSize.Width, m_selectedPipSize.Width, NumberOfPages(), MaxVisualIndicators());
+ scrollViewer.MaxWidth(scrollViewerWidth);
+ scrollViewer.MaxHeight(std::max(m_defaultPipSize.Height, m_selectedPipSize.Height));
+ }
+ else
+ {
+ const auto scrollViewerHeight = CalculateScrollViewerSize(m_defaultPipSize.Height, m_selectedPipSize.Height, NumberOfPages(), MaxVisualIndicators());
+ scrollViewer.MaxHeight(scrollViewerHeight);
+ scrollViewer.MaxWidth(std::max(m_defaultPipSize.Width, m_selectedPipSize.Width));
+ }
+ }
+}
+
+void PipsPager::UpdatePipsItems(const int numberOfPages, int maxVisualIndicators) {
+ auto const pipsListSize = int(m_pipsPagerItems.Size());
+
+ if (numberOfPages == 0 || maxVisualIndicators == 0)
+ {
+ m_pipsPagerItems.Clear();
+ }
+ /* Inifinite number of pages case */
+ else if (numberOfPages < 0)
+ {
+ /* Treat negative max visual indicators as 0*/
+ auto const minNumberOfElements = std::max(SelectedPageIndex() + 1, std::max(0, maxVisualIndicators));
+ if (minNumberOfElements > pipsListSize)
+ {
+ for (int i = pipsListSize; i < minNumberOfElements; i++)
+ {
+ m_pipsPagerItems.Append(winrt::box_value(i + 1));
+ }
+ }
+ else if (SelectedPageIndex() == pipsListSize - 1) {
+ m_pipsPagerItems.Append(winrt::box_value(pipsListSize + 1));
+ }
+ }
+ else if (pipsListSize < numberOfPages)
+ {
+ for (int i = pipsListSize; i < numberOfPages; i++)
+ {
+ m_pipsPagerItems.Append(winrt::box_value(i + 1));
+ }
+ }
+ else {
+ for (int i = numberOfPages; i < pipsListSize; i++)
+ {
+ m_pipsPagerItems.RemoveAtEnd();
+ }
+ }
+}
+
+void PipsPager::OnElementPrepared(winrt::ItemsRepeater sender, winrt::ItemsRepeaterElementPreparedEventArgs args)
+{
+ if (auto const element = args.Element())
+ {
+ if (const auto pip = element.try_as())
+ {
+ auto const index = args.Index();
+ if (index != SelectedPageIndex())
+ {
+ pip.Style(DefaultIndicatorButtonStyle());
+ }
+
+ // Narrator says: Page 5, Button 5 of 30. Is it expected behavior?
+ winrt::AutomationProperties::SetName(pip, ResourceAccessor::GetLocalizedStringResource(SR_PipsPagerPageText) + L" " + winrt::to_hstring(index + 1));
+ winrt::AutomationProperties::SetPositionInSet(pip, index + 1);
+ winrt::AutomationProperties::SetSizeOfSet(pip, NumberOfPages());
+
+ auto pciRevokers = winrt::make_self();
+ pciRevokers->clickRevoker = pip.Click(winrt::auto_revoke,
+ [this, index](auto const& sender, auto const& args)
+ {
+ if (const auto repeater = m_pipsPagerRepeater.get()) {
+ if (const auto button = sender.try_as())
+ {
+ SelectedPageIndex(repeater.GetElementIndex(button));
+ }
+ }
+ }
+ );
+ pip.SetValue(s_pipButtonHandlersProperty, pciRevokers.as());
+ }
+ }
+}
+
+void PipsPager::OnElementIndexChanged(const winrt::ItemsRepeater&, const winrt::ItemsRepeaterElementIndexChangedEventArgs& args)
+{
+ if (auto const pip = args.Element())
+ {
+ auto const newIndex = args.NewIndex();
+ winrt::AutomationProperties::SetName(pip, ResourceAccessor::GetLocalizedStringResource(SR_PipsPagerPageText) + L" " + winrt::to_hstring(newIndex + 1));
+ winrt::AutomationProperties::SetPositionInSet(pip, newIndex + 1);
+ }
+}
+
+void PipsPager::OnMaxVisualIndicatorsChanged()
+{
+ const auto numberOfPages = NumberOfPages();
+ if (numberOfPages < 0) {
+ UpdatePipsItems(numberOfPages, MaxVisualIndicators());
+ }
+ SetScrollViewerMaxSize();
+ UpdateSelectedPip(SelectedPageIndex());
+ UpdateNavigationButtonVisualStates();
+}
+
+void PipsPager::OnNumberOfPagesChanged()
+{
+ const int numberOfPages = NumberOfPages();
+ const int selectedPageIndex = SelectedPageIndex();
+ UpdateSizeOfSetForElements(numberOfPages);
+ UpdatePipsItems(numberOfPages, MaxVisualIndicators());
+ SetScrollViewerMaxSize();
+ if (SelectedPageIndex() > numberOfPages - 1 && numberOfPages > -1)
+ {
+ SelectedPageIndex(numberOfPages - 1);
+ }
+ else
+ {
+ UpdateSelectedPip(selectedPageIndex);
+ UpdateNavigationButtonVisualStates();
+ }
+}
+
+void PipsPager::OnSelectedPageIndexChanged(const int oldValue)
+{
+ // If we don't have any pages, there is nothing we should do.
+ // Ensure that SelectedPageIndex will end up in the valid range of values
+ // Special case is NumberOfPages being 0, in that case, don't verify upperbound restrictions
+ if (SelectedPageIndex() > NumberOfPages() - 1 && NumberOfPages() > 0)
+ {
+ SelectedPageIndex(NumberOfPages() - 1);
+ }
+ else if (SelectedPageIndex() < 0)
+ {
+ SelectedPageIndex(0);
+ }
+ else {
+ // Now handle the value changes
+ m_lastSelectedPageIndex = oldValue;
+
+ // Fire value property change for UIA
+ if (const auto peer = winrt::FrameworkElementAutomationPeer::FromElement(*this).try_as())
+ {
+ winrt::get_self(peer)->RaiseSelectionChanged(m_lastSelectedPageIndex, SelectedPageIndex());
+ }
+ if (NumberOfPages() < 0) {
+ UpdatePipsItems(NumberOfPages(), MaxVisualIndicators());
+ }
+ UpdateSelectedPip(SelectedPageIndex());
+ UpdateNavigationButtonVisualStates();
+ RaiseSelectedIndexChanged();
+ }
+}
+
+void PipsPager::OnOrientationChanged()
+{
+ if (Orientation() == winrt::Orientation::Horizontal)
+ {
+ winrt::VisualStateManager::GoToState(*this, c_pipsPagerHorizontalOrientationVisualState, false);
+ }
+ else
+ {
+ winrt::VisualStateManager::GoToState(*this, c_pipsPagerVerticalOrientationVisualState, false);
+ }
+ SetScrollViewerMaxSize();
+ UpdateSelectedPip(SelectedPageIndex());
+
+}
+
+void PipsPager::OnNavigationButtonVisibilityChanged(const ButtonVisibility visibility, const wstring_view& collapsedStateName, const wstring_view& disabledStateName) {
+ if (visibility == ButtonVisibility::Collapsed)
+ {
+ winrt::VisualStateManager::GoToState(*this, collapsedStateName, false);
+ winrt::VisualStateManager::GoToState(*this, disabledStateName, false);
+ }
+ else
+ {
+ UpdateNavigationButtonVisualStates();
+ }
+}
+
+void PipsPager::OnPreviousButtonClicked(const IInspectable& sender, const winrt::RoutedEventArgs& e)
+{
+ // In this method, SelectedPageIndex is always greater than 0.
+ SelectedPageIndex(SelectedPageIndex() - 1);
+}
+
+void PipsPager::OnNextButtonClicked(const IInspectable& sender, const winrt::RoutedEventArgs& e)
+{
+ // In this method, SelectedPageIndex is always less than maximum.
+ SelectedPageIndex(SelectedPageIndex() + 1);
+}
+
+void PipsPager::OnPropertyChanged(const winrt::DependencyPropertyChangedEventArgs& args)
+{
+ winrt::IDependencyProperty property = args.Property();
+ if (this->Template() != nullptr)
+ {
+ if (property == NumberOfPagesProperty())
+ {
+ OnNumberOfPagesChanged();
+ }
+ else if (property == SelectedPageIndexProperty())
+ {
+ OnSelectedPageIndexChanged(winrt::unbox_value(args.OldValue()));
+ }
+ else if (property == MaxVisualIndicatorsProperty()) {
+ OnMaxVisualIndicatorsChanged();
+ }
+ else if (property == PreviousButtonVisibilityProperty())
+ {
+ OnNavigationButtonVisibilityChanged(PreviousButtonVisibility(), c_previousPageButtonCollapsedVisualState, c_previousPageButtonDisabledVisualState);
+ }
+ else if (property == NextButtonVisibilityProperty())
+ {
+ OnNavigationButtonVisibilityChanged(NextButtonVisibility(), c_nextPageButtonCollapsedVisualState, c_nextPageButtonDisabledVisualState);
+ }
+ else if (property == DefaultIndicatorButtonStyleProperty())
+ {
+ m_defaultPipSize = GetDesiredPipSize(DefaultIndicatorButtonStyle());
+ SetScrollViewerMaxSize();
+ UpdateSelectedPip(SelectedPageIndex());
+ }
+ else if (property == SelectedIndicatorButtonStyleProperty())
+ {
+ m_selectedPipSize = GetDesiredPipSize(SelectedIndicatorButtonStyle());
+ SetScrollViewerMaxSize();
+ UpdateSelectedPip(SelectedPageIndex());
+ }
+ else if (property == OrientationProperty())
+ {
+ OnOrientationChanged();
+ }
+ }
+}
+
+winrt::AutomationPeer PipsPager::OnCreateAutomationPeer()
+{
+ return winrt::make(*this);
+}
+
+void PipsPager::UpdateSizeOfSetForElements(const int numberOfPages) {
+ if(auto const repeater = m_pipsPagerRepeater.get())
+ {
+ for (int i = 0; i < numberOfPages; i++)
+ {
+ if (auto const pip = repeater.TryGetElement(i))
+ {
+ winrt::AutomationProperties::SetSizeOfSet(pip, numberOfPages);
+ }
+ }
+ }
+}
diff --git a/dev/PipsPager/PipsPager.h b/dev/PipsPager/PipsPager.h
new file mode 100755
index 0000000000..4e484b3f45
--- /dev/null
+++ b/dev/PipsPager/PipsPager.h
@@ -0,0 +1,96 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+#pragma once
+
+#include "pch.h"
+#include "common.h"
+#include "PipsPager.g.h"
+#include "PipsPager.properties.h"
+
+class PipsPagerViewItemRevokers : public winrt::implements
+{
+public:
+ winrt::Button::Click_revoker clickRevoker{};
+};
+
+class PipsPager :
+ public ReferenceTracker,
+ public PipsPagerProperties
+{
+public:
+ PipsPager();
+
+ /* IFrameworkElement */
+ void OnApplyTemplate();
+ void OnPropertyChanged(const winrt::DependencyPropertyChangedEventArgs& args);
+
+ /* Accessibility */
+ winrt::AutomationPeer OnCreateAutomationPeer();
+ void UpdateSizeOfSetForElements(const int numberOfPages);
+
+ void OnPointerEntered(const winrt::PointerRoutedEventArgs& args);
+ void OnPointerExited(const winrt::PointerRoutedEventArgs& args);
+ void OnPointerCanceled(const winrt::PointerRoutedEventArgs& args);
+ void OnKeyDown(const winrt::KeyRoutedEventArgs& args);
+
+ /* Property changed handlers */
+ void OnNumberOfPagesChanged();
+ void OnSelectedPageIndexChanged(const int oldValue);
+ void OnMaxVisualIndicatorsChanged();
+ void OnNavigationButtonVisibilityChanged(
+ const winrt::PipsPagerButtonVisibility visibility,
+ const wstring_view& collapsedStateName,
+ const wstring_view& disabledStateName);
+ void OnOrientationChanged();
+
+ /* Dependency property for pip buttons revokers */
+ GlobalDependencyProperty s_pipButtonHandlersProperty;
+
+private:
+ /* UI updating */
+ void UpdateNavigationButtonVisualStates();
+ void SetScrollViewerMaxSize();
+ bool IsOutOfControlBounds(const winrt::Point& point);
+ void UpdateIndividualNavigationButtonVisualState(
+ const bool hiddenOnEdgeCondition,
+ const winrt::PipsPagerButtonVisibility visibility,
+ const wstring_view& visibleStateName,
+ const wstring_view& hiddenStateName,
+ const wstring_view& enabledStateName,
+ const wstring_view& disabledStateName);
+ winrt::Size GetDesiredPipSize(const winrt::Style& style);
+ void ScrollToCenterOfViewport(const winrt::UIElement sender, const int index);
+ double CalculateScrollViewerSize(const double defaultPipSize, const double selectedPipSize, const int numberOfPages, int maxVisualIndicators);
+ void UpdateSelectedPip(const int index);
+
+ /* Eventing */
+ void RaiseSelectedIndexChanged();
+
+ /* Interaction event listeners */
+ void OnPreviousButtonClicked(const IInspectable& sender, const winrt::RoutedEventArgs& args);
+ void OnNextButtonClicked(const IInspectable& sender, const winrt::RoutedEventArgs& args);
+
+ /* Pips Logic */
+ void UpdatePipsItems(const int numberOfPages, int maxVisualIndicators);
+ void OnElementPrepared(winrt::ItemsRepeater sender, winrt::ItemsRepeaterElementPreparedEventArgs args);
+ void OnElementIndexChanged(const winrt::ItemsRepeater& repeater, const winrt::ItemsRepeaterElementIndexChangedEventArgs& args);
+
+ /* Refs */
+ tracker_ref m_pipsPagerRepeater{ this };
+ tracker_ref m_pipsPagerScrollViewer{ this };
+
+ /* Revokers */
+ winrt::Button::Click_revoker m_previousPageButtonClickRevoker{};
+ winrt::Button::Click_revoker m_nextPageButtonClickRevoker{};
+ winrt::ItemsRepeater::ElementPrepared_revoker m_pipsPagerElementPreparedRevoker{};
+
+ /* Items */
+ winrt::IObservableVector m_pipsPagerItems{};
+
+ /* Additional variables class variables*/
+ winrt::Size m_defaultPipSize{ 0.0,0.0 };
+ winrt::Size m_selectedPipSize{ 0.0, 0.0 };
+ int m_lastSelectedPageIndex{ -1 };
+ bool m_isPointerOver{ false };
+};
diff --git a/dev/PipsPager/PipsPager.idl b/dev/PipsPager/PipsPager.idl
new file mode 100755
index 0000000000..0818b68307
--- /dev/null
+++ b/dev/PipsPager/PipsPager.idl
@@ -0,0 +1,78 @@
+namespace MU_XC_NAMESPACE
+{
+
+[WUXC_VERSION_PREVIEW]
+[webhosthidden]
+enum PipsPagerButtonVisibility
+{
+ Visible,
+ VisibleOnHover,
+ Collapsed
+};
+
+[WUXC_VERSION_PREVIEW]
+[webhosthidden]
+runtimeclass PipsPagerSelectedIndexChangedEventArgs
+{
+ Int32 NewPageIndex{get; };
+ Int32 PreviousPageIndex{get; };
+};
+
+[WUXC_VERSION_PREVIEW]
+[webhosthidden]
+[MUX_PROPERTY_NEEDS_DP_FIELD]
+unsealed runtimeclass PipsPagerTemplateSettings : Windows.UI.Xaml.DependencyObject
+{
+ PipsPagerTemplateSettings();
+ Windows.Foundation.Collections.IVector