From 0d4065ff2392048a4fd0ff5036fe2b73b09ad7cf Mon Sep 17 00:00:00 2001 From: Marcel Wagner Date: Mon, 16 Mar 2020 17:41:19 +0100 Subject: [PATCH] TabView Compact TabViewWidthMode and new CloseButtonOverlayMode property (#2016) * Add/update enums * Add properties * Update test page to handle new values * Add initial logic for handling switching to and from compact width mode * Add API test for compact width mode * Initial feature set for CloseButtonOverlayMode * Add initial CloseButtonOverlayMode tests * Update CloseButtonOverlayMode Auto behavior * Revert inner loop sln * CR feedback * Switch to visual states for close button * CR feedback * Set correct package availability level for TabView close button hover mode * Add necessary WUXC_VERSION_PREVIEW tags * CR feedback * CR feedback the second * Revert failing A PI test * Switch to lambdas to use const * Update names in CloseButtonOverlaymode API --- MUXControls.sln | 6 + dev/Generated/TabView.properties.cpp | 31 +++++ dev/Generated/TabView.properties.h | 9 ++ dev/TabView/APITests/TabViewTests.cs | 119 ++++++++++++++++++ .../APITests/TabView_APITests.projitems | 15 +++ dev/TabView/APITests/TabView_APITests.shproj | 13 ++ dev/TabView/InteractionTests/TabViewTests.cs | 43 +++++++ dev/TabView/TabView.cpp | 76 ++++++++--- dev/TabView/TabView.h | 1 + dev/TabView/TabView.idl | 21 ++++ dev/TabView/TabView.xaml | 23 +++- dev/TabView/TabViewItem.cpp | 64 +++++++++- dev/TabView/TabViewItem.h | 6 + dev/TabView/TestUI/TabViewPage.xaml | 11 +- dev/TabView/TestUI/TabViewPage.xaml.cs | 18 ++- .../MUXControlsTestApp.Shared.targets | 1 + 16 files changed, 432 insertions(+), 25 deletions(-) create mode 100644 dev/TabView/APITests/TabViewTests.cs create mode 100644 dev/TabView/APITests/TabView_APITests.projitems create mode 100644 dev/TabView/APITests/TabView_APITests.shproj diff --git a/MUXControls.sln b/MUXControls.sln index bdbff110ad..59f4a7b062 100644 --- a/MUXControls.sln +++ b/MUXControls.sln @@ -630,6 +630,8 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "RadialGradientBrush_TestUI" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "RadialGradientBrush_InteractionTests", "dev\RadialGradientBrush\InteractionTests\RadialGradientBrush_InteractionTests.shproj", "{74D18B1B-5F6B-4534-945B-131E8E3206FB}" EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "TabView_APITests", "dev\TabView\APITests\TabView_APITests.shproj", "{2F4E95E9-F729-481C-B9AA-C9BEC91AE395}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution dev\ComboBox\ComboBox.vcxitems*{00523caf-422a-4185-9392-d374b72a019a}*SharedItemsImports = 9 @@ -666,6 +668,7 @@ Global dev\SplitButton\TestUI\SplitButton_TestUI.projitems*{280c91f4-96b5-4bde-9e02-e573e1def583}*SharedItemsImports = 13 dev\Repeater\TestUI\Repeater_TestUI.projitems*{2ed883f5-20db-4445-8c96-517a21e5e657}*SharedItemsImports = 13 dev\MenuFlyout\TestUI\MenuFlyout_TestUI.projitems*{2ef860e2-8766-41fc-bde2-e6b18bb8c206}*SharedItemsImports = 13 + dev\TabView\APITests\TabView_APITests.projitems*{2f4e95e9-f729-481c-b9aa-c9bec91ae395}*SharedItemsImports = 13 dev\ParallaxView\ParallaxView.vcxitems*{3095445a-afcd-5154-ac36-9770e6ec1aa5}*SharedItemsImports = 9 dev\RadioMenuFlyoutItem\RadioMenuFlyoutItem.vcxitems*{3353a4a7-87b3-4e43-8f8d-43c7380d1d56}*SharedItemsImports = 9 dev\Lights\Lights.vcxitems*{3479a3ae-2854-4bec-80ab-eab0772cb90a}*SharedItemsImports = 9 @@ -927,6 +930,7 @@ Global dev\SwipeControl\SwipeControl_APITests\SwipeControl_APITests.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4 dev\SwipeControl\SwipeControl_TestUI\SwipeControl_TestUI.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4 dev\TabView\TestUI\TabView_TestUI.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4 + dev\TabView\APITests\TabView_APITests.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4 dev\TeachingTip\APITests\TeachingTip_APITests.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4 dev\TeachingTip\TestUI\TeachingTip_TestUI.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4 dev\TimePicker\TestUI\TimePicker_TestUI.projitems*{dedc1e4f-cfa5-4443-83eb-e79d425df7e7}*SharedItemsImports = 4 @@ -1015,6 +1019,7 @@ Global dev\SwipeControl\SwipeControl_APITests\SwipeControl_APITests.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4 dev\SwipeControl\SwipeControl_TestUI\SwipeControl_TestUI.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4 dev\TabView\TestUI\TabView_TestUI.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4 + dev\TabView\APITests\TabView_APITests.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4 dev\TeachingTip\APITests\TeachingTip_APITests.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4 dev\TeachingTip\TestUI\TeachingTip_TestUI.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4 dev\TimePicker\TestUI\TimePicker_TestUI.projitems*{fbc396f5-26dd-4ca3-981e-c7bc9fea4546}*SharedItemsImports = 4 @@ -1615,6 +1620,7 @@ Global {8B056B8F-C1AB-4A80-BD17-DEACE9897E6A} = {0115F80C-AB97-412D-85D9-33A5188F8907} {AE308818-AF18-48BA-BF33-89779083D297} = {0115F80C-AB97-412D-85D9-33A5188F8907} {74D18B1B-5F6B-4534-945B-131E8E3206FB} = {0115F80C-AB97-412D-85D9-33A5188F8907} + {2F4E95E9-F729-481C-B9AA-C9BEC91AE395} = {B3E64837-A5E4-49CB-97FF-A365307B9191} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D93836AB-52D3-4DE2-AE25-23F26F55ECED} diff --git a/dev/Generated/TabView.properties.cpp b/dev/Generated/TabView.properties.cpp index 8851766bc8..449ee13e40 100644 --- a/dev/Generated/TabView.properties.cpp +++ b/dev/Generated/TabView.properties.cpp @@ -18,6 +18,7 @@ GlobalDependencyProperty TabViewProperties::s_AddTabButtonCommandParameterProper GlobalDependencyProperty TabViewProperties::s_AllowDropTabsProperty{ nullptr }; GlobalDependencyProperty TabViewProperties::s_CanDragTabsProperty{ nullptr }; GlobalDependencyProperty TabViewProperties::s_CanReorderTabsProperty{ nullptr }; +GlobalDependencyProperty TabViewProperties::s_CloseButtonOverlayModeProperty{ nullptr }; GlobalDependencyProperty TabViewProperties::s_IsAddTabButtonVisibleProperty{ nullptr }; GlobalDependencyProperty TabViewProperties::s_SelectedIndexProperty{ nullptr }; GlobalDependencyProperty TabViewProperties::s_SelectedItemProperty{ nullptr }; @@ -102,6 +103,17 @@ void TabViewProperties::EnsureProperties() ValueHelper::BoxValueIfNecessary(true), nullptr); } + if (!s_CloseButtonOverlayModeProperty) + { + s_CloseButtonOverlayModeProperty = + InitializeDependencyProperty( + L"CloseButtonOverlayMode", + winrt::name_of(), + winrt::name_of(), + false /* isAttached */, + ValueHelper::BoxValueIfNecessary(winrt::TabViewCloseButtonOverlayMode::Auto), + winrt::PropertyChangedCallback(&OnCloseButtonOverlayModePropertyChanged)); + } if (!s_IsAddTabButtonVisibleProperty) { s_IsAddTabButtonVisibleProperty = @@ -243,6 +255,7 @@ void TabViewProperties::ClearProperties() s_AllowDropTabsProperty = nullptr; s_CanDragTabsProperty = nullptr; s_CanReorderTabsProperty = nullptr; + s_CloseButtonOverlayModeProperty = nullptr; s_IsAddTabButtonVisibleProperty = nullptr; s_SelectedIndexProperty = nullptr; s_SelectedItemProperty = nullptr; @@ -257,6 +270,14 @@ void TabViewProperties::ClearProperties() s_TabWidthModeProperty = nullptr; } +void TabViewProperties::OnCloseButtonOverlayModePropertyChanged( + winrt::DependencyObject const& sender, + winrt::DependencyPropertyChangedEventArgs const& args) +{ + auto owner = sender.as(); + winrt::get_self(owner)->OnCloseButtonOverlayModePropertyChanged(args); +} + void TabViewProperties::OnSelectedIndexPropertyChanged( winrt::DependencyObject const& sender, winrt::DependencyPropertyChangedEventArgs const& args) @@ -331,6 +352,16 @@ bool TabViewProperties::CanReorderTabs() return ValueHelper::CastOrUnbox(static_cast(this)->GetValue(s_CanReorderTabsProperty)); } +void TabViewProperties::CloseButtonOverlayMode(winrt::TabViewCloseButtonOverlayMode const& value) +{ + static_cast(this)->SetValue(s_CloseButtonOverlayModeProperty, ValueHelper::BoxValueIfNecessary(value)); +} + +winrt::TabViewCloseButtonOverlayMode TabViewProperties::CloseButtonOverlayMode() +{ + return ValueHelper::CastOrUnbox(static_cast(this)->GetValue(s_CloseButtonOverlayModeProperty)); +} + void TabViewProperties::IsAddTabButtonVisible(bool value) { static_cast(this)->SetValue(s_IsAddTabButtonVisibleProperty, ValueHelper::BoxValueIfNecessary(value)); diff --git a/dev/Generated/TabView.properties.h b/dev/Generated/TabView.properties.h index 63ac06cce1..b8e491baa7 100644 --- a/dev/Generated/TabView.properties.h +++ b/dev/Generated/TabView.properties.h @@ -24,6 +24,9 @@ class TabViewProperties void CanReorderTabs(bool value); bool CanReorderTabs(); + void CloseButtonOverlayMode(winrt::TabViewCloseButtonOverlayMode const& value); + winrt::TabViewCloseButtonOverlayMode CloseButtonOverlayMode(); + void IsAddTabButtonVisible(bool value); bool IsAddTabButtonVisible(); @@ -65,6 +68,7 @@ class TabViewProperties static winrt::DependencyProperty AllowDropTabsProperty() { return s_AllowDropTabsProperty; } static winrt::DependencyProperty CanDragTabsProperty() { return s_CanDragTabsProperty; } static winrt::DependencyProperty CanReorderTabsProperty() { return s_CanReorderTabsProperty; } + static winrt::DependencyProperty CloseButtonOverlayModeProperty() { return s_CloseButtonOverlayModeProperty; } static winrt::DependencyProperty IsAddTabButtonVisibleProperty() { return s_IsAddTabButtonVisibleProperty; } static winrt::DependencyProperty SelectedIndexProperty() { return s_SelectedIndexProperty; } static winrt::DependencyProperty SelectedItemProperty() { return s_SelectedItemProperty; } @@ -83,6 +87,7 @@ class TabViewProperties static GlobalDependencyProperty s_AllowDropTabsProperty; static GlobalDependencyProperty s_CanDragTabsProperty; static GlobalDependencyProperty s_CanReorderTabsProperty; + static GlobalDependencyProperty s_CloseButtonOverlayModeProperty; static GlobalDependencyProperty s_IsAddTabButtonVisibleProperty; static GlobalDependencyProperty s_SelectedIndexProperty; static GlobalDependencyProperty s_SelectedItemProperty; @@ -128,6 +133,10 @@ class TabViewProperties static void EnsureProperties(); static void ClearProperties(); + static void OnCloseButtonOverlayModePropertyChanged( + winrt::DependencyObject const& sender, + winrt::DependencyPropertyChangedEventArgs const& args); + static void OnSelectedIndexPropertyChanged( winrt::DependencyObject const& sender, winrt::DependencyPropertyChangedEventArgs const& args); diff --git a/dev/TabView/APITests/TabViewTests.cs b/dev/TabView/APITests/TabViewTests.cs new file mode 100644 index 0000000000..89c945bfc9 --- /dev/null +++ b/dev/TabView/APITests/TabViewTests.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using MUXControlsTestApp.Utilities; +using System; +using System.Threading; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Markup; +using Windows.UI.Xaml.Media; +using Common; +using Microsoft.UI.Xaml.Controls; +using System.Collections.Generic; + +#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 Symbol = Windows.UI.Xaml.Controls.Symbol; + +namespace Windows.UI.Xaml.Tests.MUXControls.ApiTests +{ + + [TestClass] + public class TabViewTests : ApiTestBase + { + + [TestMethod] + public void VerifyCompactTabWidthVisualStates() + { + TabView tabView = null; + RunOnUIThread.Execute(() => + { + tabView = new TabView(); + Content = tabView; + + tabView.TabItems.Add(CreateTabViewItem("Item 0", Symbol.Add)); + tabView.TabItems.Add(CreateTabViewItem("Item 1", Symbol.AddFriend)); + tabView.TabItems.Add(CreateTabViewItem("Item 2")); + + tabView.SelectedIndex = 0; + tabView.SelectedItem = tabView.TabItems[0]; + (tabView.SelectedItem as TabViewItem).IsSelected = true; + Verify.AreEqual("Item 0", (tabView.SelectedItem as TabViewItem).Header); + tabView.TabWidthMode = TabViewWidthMode.Compact; + Content.UpdateLayout(); + }); + + IdleSynchronizer.Wait(); + + // Check if switching to compact updates all items correctly + RunOnUIThread.Execute(() => + { + VerifyTabWidthVisualStates(tabView.TabItems, true); + tabView.TabItems.Add(CreateTabViewItem("Item 3")); + }); + + IdleSynchronizer.Wait(); + + // Check if a newly added item has correct visual states + RunOnUIThread.Execute(() => + { + VerifyTabWidthVisualStates(tabView.TabItems, true); + tabView.TabWidthMode = TabViewWidthMode.Equal; + }); + + IdleSynchronizer.Wait(); + + // Switch back to non compact and check if every item has the correct visual state + RunOnUIThread.Execute(() => + { + VerifyTabWidthVisualStates(tabView.TabItems, false); + }); + } + + private static void VerifyTabWidthVisualStates(IList items, bool isCompact) + { + foreach (var item in items) + { + var tabItem = item as TabViewItem; + if (tabItem.IsSelected || !isCompact) + { + VisualStateHelper.ContainsVisualState(tabItem, "StandardWidth"); + } + else + { + VisualStateHelper.ContainsVisualState(tabItem, "Compact"); + } + } + } + + private static TabViewItem CreateTabViewItem(string name, Symbol icon, bool closable = true, bool enabled = true) + { + var tabViewItem = new TabViewItem(); + + tabViewItem.Header = name; + tabViewItem.IconSource = new Microsoft.UI.Xaml.Controls.SymbolIconSource() { Symbol = icon }; + tabViewItem.IsClosable = closable; + tabViewItem.IsEnabled = enabled; + + return tabViewItem; + } + + private static TabViewItem CreateTabViewItem(string name, bool closable = true, bool enabled = true) + { + var tabViewItem = new TabViewItem(); + + tabViewItem.Header = name; + tabViewItem.IsClosable = closable; + tabViewItem.IsEnabled = enabled; + + return tabViewItem; + } + } +} diff --git a/dev/TabView/APITests/TabView_APITests.projitems b/dev/TabView/APITests/TabView_APITests.projitems new file mode 100644 index 0000000000..e8f380a56b --- /dev/null +++ b/dev/TabView/APITests/TabView_APITests.projitems @@ -0,0 +1,15 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 2f4e95e9-f729-481c-b9aa-c9bec91ae395 + + + TabView_APITests + + + + + \ No newline at end of file diff --git a/dev/TabView/APITests/TabView_APITests.shproj b/dev/TabView/APITests/TabView_APITests.shproj new file mode 100644 index 0000000000..92c460744d --- /dev/null +++ b/dev/TabView/APITests/TabView_APITests.shproj @@ -0,0 +1,13 @@ + + + + 2f4e95e9-f729-481c-b9aa-c9bec91ae395 + 14.0 + + + + + + + + diff --git a/dev/TabView/InteractionTests/TabViewTests.cs b/dev/TabView/InteractionTests/TabViewTests.cs index 11240588f2..2a9fb2bfce 100644 --- a/dev/TabView/InteractionTests/TabViewTests.cs +++ b/dev/TabView/InteractionTests/TabViewTests.cs @@ -408,6 +408,49 @@ public void KeyboardTest() } } + [TestMethod] + public void CloseButtonOverlayModeTests() + { + using(var setup = new TestSetupHelper("TabView Tests")) + { + ComboBox closeButtonOverlayModeComboBox = FindElement.ByName("CloseButtonOverlayModeCombobox"); + closeButtonOverlayModeComboBox.SelectItemByName("OnHover"); + Wait.ForIdle(); + + Button closeUnselectedButton = FindCloseButton(FindElement.ByName("LongHeaderTab")); + Button closeSelectedButton = FindCloseButton(FindElement.ByName("FirstTab")); + Verify.IsNull(closeUnselectedButton); + Verify.IsNotNull(closeSelectedButton); + + closeButtonOverlayModeComboBox.SelectItemByName("Always"); + Wait.ForIdle(); + + // Verifiying "Always" works correctly + closeSelectedButton = FindCloseButton(FindElement.ByName("FirstTab")); + closeUnselectedButton = FindCloseButton(FindElement.ByName("LongHeaderTab")); + Verify.IsNotNull(closeUnselectedButton); + Verify.IsNotNull(closeSelectedButton); + + // Verifiying "OnHover" works correctly + closeButtonOverlayModeComboBox.SelectItemByName("OnHover"); + Wait.ForIdle(); + + closeSelectedButton = FindCloseButton(FindElement.ByName("FirstTab")); + closeUnselectedButton = FindCloseButton(FindElement.ByName("LongHeaderTab")); + Verify.IsNull(closeUnselectedButton); + Verify.IsNotNull(closeSelectedButton); + + // Verifiying "Auto" works correctly + closeButtonOverlayModeComboBox.SelectItemByName("Auto"); + Wait.ForIdle(); + + closeSelectedButton = FindCloseButton(FindElement.ByName("FirstTab")); + closeUnselectedButton = FindCloseButton(FindElement.ByName("LongHeaderTab")); + Verify.IsNotNull(closeUnselectedButton); + Verify.IsNotNull(closeSelectedButton); + + } + } [TestMethod] public void GamePadTest() diff --git a/dev/TabView/TabView.cpp b/dev/TabView/TabView.cpp index 83de3241ea..2b36fcccce 100644 --- a/dev/TabView/TabView.cpp +++ b/dev/TabView/TabView.cpp @@ -214,6 +214,45 @@ void TabView::OnSelectedItemPropertyChanged(const winrt::DependencyPropertyChang void TabView::OnTabWidthModePropertyChanged(const winrt::DependencyPropertyChangedEventArgs&) { UpdateTabWidths(); + + // Switch the visual states of all tab items to the correct TabViewWidthMode + for (auto&& item : TabItems()) + { + auto const tvi = [item, this]() + { + if (auto tabViewItem = item.try_as()) + { + return tabViewItem; + } + return ContainerFromItem(item).try_as(); + }(); + + if (tvi) + { + tvi->OnTabViewWidthModeChanged(TabWidthMode()); + } + } +} + +void TabView::OnCloseButtonOverlayModePropertyChanged(const winrt::DependencyPropertyChangedEventArgs&) +{ + // Switch the visual states of all tab items to to the correct closebutton overlay mode + for (auto&& item : TabItems()) + { + auto const tvi = [item, this]() + { + if (auto tabViewItem = item.try_as()) + { + return tabViewItem; + } + return ContainerFromItem(item).try_as(); + }(); + + if (tvi) + { + tvi->OnCloseButtonOverlayModeChanged(CloseButtonOverlayMode()); + } + } } void TabView::OnAddButtonClick(const winrt::IInspectable&, const winrt::RoutedEventArgs& args) @@ -577,24 +616,7 @@ void TabView::UpdateTabWidths() // Size can be 0 when window is first created; in that case, skip calculations; we'll get a new size soon if (availableWidth > 0) { - if (TabWidthMode() == winrt::TabViewWidthMode::SizeToContent) - { - tabColumn.MaxWidth(availableWidth); - tabColumn.Width(winrt::GridLengthHelper::FromValueAndType(1.0, winrt::GridUnitType::Auto)); - if (auto listview = m_listView.get()) - { - listview.MaxWidth(availableWidth); - - // Calculate if the scroll buttons should be visible. - if (auto itemsPresenter = m_itemsPresenter.get()) - { - winrt::FxScrollViewer::SetHorizontalScrollBarVisibility(listview, itemsPresenter.ActualWidth() > availableWidth - ? winrt::Windows::UI::Xaml::Controls::ScrollBarVisibility::Visible - : winrt::Windows::UI::Xaml::Controls::ScrollBarVisibility::Hidden); - } - } - } - else if (TabWidthMode() == winrt::TabViewWidthMode::Equal) + if (TabWidthMode() == winrt::TabViewWidthMode::Equal) { auto const minTabWidth = unbox_value(SharedHelpers::FindInApplicationResources(c_tabViewItemMinWidthName, box_value(c_tabMinimumWidth))); auto const maxTabWidth = unbox_value(SharedHelpers::FindInApplicationResources(c_tabViewItemMaxWidthName, box_value(c_tabMaximumWidth))); @@ -625,6 +647,24 @@ void TabView::UpdateTabWidths() } } } + else + { + // Case: TabWidthMode "Compact" or "FitToContent" + tabColumn.MaxWidth(availableWidth); + tabColumn.Width(winrt::GridLengthHelper::FromValueAndType(1.0, winrt::GridUnitType::Auto)); + if (auto listview = m_listView.get()) + { + listview.MaxWidth(availableWidth); + + // Calculate if the scroll buttons should be visible. + if (auto itemsPresenter = m_itemsPresenter.get()) + { + winrt::FxScrollViewer::SetHorizontalScrollBarVisibility(listview, itemsPresenter.ActualWidth() > availableWidth + ? winrt::Windows::UI::Xaml::Controls::ScrollBarVisibility::Visible + : winrt::Windows::UI::Xaml::Controls::ScrollBarVisibility::Hidden); + } + } + } } } } diff --git a/dev/TabView/TabView.h b/dev/TabView/TabView.h index 9252074585..a03015afe4 100644 --- a/dev/TabView/TabView.h +++ b/dev/TabView/TabView.h @@ -103,6 +103,7 @@ class TabView : void OnKeyDown(winrt::KeyRoutedEventArgs const& e); // Internal + void OnCloseButtonOverlayModePropertyChanged(const winrt::DependencyPropertyChangedEventArgs& args); void OnTabWidthModePropertyChanged(const winrt::DependencyPropertyChangedEventArgs& args); void OnSelectedIndexPropertyChanged(const winrt::DependencyPropertyChangedEventArgs& args); void OnSelectedItemPropertyChanged(const winrt::DependencyPropertyChangedEventArgs& args); diff --git a/dev/TabView/TabView.idl b/dev/TabView/TabView.idl index c9b824bba9..1607a14189 100644 --- a/dev/TabView/TabView.idl +++ b/dev/TabView/TabView.idl @@ -7,6 +7,16 @@ enum TabViewWidthMode { Equal = 0, SizeToContent = 1, + Compact = 2, +}; + +[WUXC_VERSION_PREVIEW] +[webhosthidden] +enum TabViewCloseButtonOverlayMode +{ + Auto = 0, + OnHover = 1, + Always = 2, }; [WUXC_VERSION_MUXONLY] @@ -55,6 +65,13 @@ unsealed runtimeclass TabView : Windows.UI.Xaml.Controls.Control [MUX_PROPERTY_CHANGED_CALLBACK(TRUE)] TabViewWidthMode TabWidthMode{ get; set; }; + [WUXC_VERSION_PREVIEW] + { + [MUX_DEFAULT_VALUE("winrt::TabViewCloseButtonOverlayMode::Auto")] + [MUX_PROPERTY_CHANGED_CALLBACK(TRUE)] + TabViewCloseButtonOverlayMode CloseButtonOverlayMode{ get; set; }; + } + Object TabStripHeader{ get; set; }; Windows.UI.Xaml.DataTemplate TabStripHeaderTemplate{ get; set; }; @@ -118,6 +135,10 @@ unsealed runtimeclass TabView : Windows.UI.Xaml.Controls.Control static Windows.UI.Xaml.DependencyProperty TabItemsProperty{ get; }; static Windows.UI.Xaml.DependencyProperty TabItemTemplateProperty{ get; }; static Windows.UI.Xaml.DependencyProperty TabItemTemplateSelectorProperty{ get; }; + [WUXC_VERSION_PREVIEW] + { + static Windows.UI.Xaml.DependencyProperty CloseButtonOverlayModeProperty{ get; }; + } static Windows.UI.Xaml.DependencyProperty CanDragTabsProperty{ get; }; static Windows.UI.Xaml.DependencyProperty CanReorderTabsProperty{ get; }; static Windows.UI.Xaml.DependencyProperty AllowDropTabsProperty{ get; }; diff --git a/dev/TabView/TabView.xaml b/dev/TabView/TabView.xaml index 95f928a1e2..75cfa9bf94 100644 --- a/dev/TabView/TabView.xaml +++ b/dev/TabView/TabView.xaml @@ -581,6 +581,27 @@ + + + + + + + + + + + + + + + + + + + + + - + diff --git a/dev/TabView/TabViewItem.cpp b/dev/TabView/TabViewItem.cpp index 266b6637c5..e191b46e54 100644 --- a/dev/TabView/TabViewItem.cpp +++ b/dev/TabView/TabViewItem.cpp @@ -85,6 +85,9 @@ void TabViewItem::OnApplyTemplate() void TabViewItem::OnIsSelectedPropertyChanged(const winrt::DependencyObject& sender, const winrt::DependencyProperty& args) { UpdateShadow(); + UpdateWidthModeVisualState(); + + UpdateCloseButton(); } void TabViewItem::UpdateShadow() @@ -119,12 +122,62 @@ winrt::AutomationPeer TabViewItem::OnCreateAutomationPeer() return winrt::make(*this); } +void TabViewItem::OnCloseButtonOverlayModeChanged(winrt::TabViewCloseButtonOverlayMode const& mode) +{ + m_closeButtonOverlayMode = mode; + UpdateCloseButton(); +} + +void TabViewItem::OnTabViewWidthModeChanged(winrt::TabViewWidthMode const& mode) +{ + m_tabViewWidthMode = mode; + UpdateWidthModeVisualState(); +} + void TabViewItem::UpdateCloseButton() { - if (auto && closeButton = m_closeButton.get()) + if (!IsClosable()) { - closeButton.Visibility(IsClosable() ? winrt::Visibility::Visible : winrt::Visibility::Collapsed); + winrt::VisualStateManager::GoToState(*this, L"CloseButtonCollapsed", false); + } + else + { + switch (m_closeButtonOverlayMode) + { + case winrt::TabViewCloseButtonOverlayMode::OnHover: + { + // If we only want to show the button on hover, we also show it when we are selected, otherwise hide it + if (IsSelected() || m_isPointerOver) + { + winrt::VisualStateManager::GoToState(*this, L"CloseButtonVisible", false); + } + else + { + winrt::VisualStateManager::GoToState(*this, L"CloseButtonCollapsed", false); + } + break; + } + default: + { + // Default, use "Auto" + winrt::VisualStateManager::GoToState(*this, L"CloseButtonVisible", false); + break; + } + } + } +} + +void TabViewItem::UpdateWidthModeVisualState() +{ + // Handling compact/non compact width mode + if (!IsSelected() && m_tabViewWidthMode == winrt::TabViewWidthMode::Compact) + { + winrt::VisualStateManager::GoToState(*this, L"Compact", false); + } + else + { + winrt::VisualStateManager::GoToState(*this, L"StandardWidth", false); } } @@ -245,17 +298,24 @@ void TabViewItem::OnPointerEntered(winrt::PointerRoutedEventArgs const& args) { __super::OnPointerEntered(args); + m_isPointerOver = true; + if (m_hasPointerCapture) { m_isMiddlePointerButtonPressed = true; } + + UpdateCloseButton(); } void TabViewItem::OnPointerExited(winrt::PointerRoutedEventArgs const& args) { __super::OnPointerExited(args); + m_isPointerOver = false; m_isMiddlePointerButtonPressed = false; + + UpdateCloseButton(); } void TabViewItem::OnPointerCanceled(winrt::PointerRoutedEventArgs const& args) diff --git a/dev/TabView/TabViewItem.h b/dev/TabView/TabViewItem.h index 44ebcf0d13..117c67d909 100644 --- a/dev/TabView/TabViewItem.h +++ b/dev/TabView/TabViewItem.h @@ -38,14 +38,19 @@ class TabViewItem : void OnPointerCaptureLost(winrt::PointerRoutedEventArgs const& args); void RaiseRequestClose(TabViewTabCloseRequestedEventArgs const& args); + void OnTabViewWidthModeChanged(winrt::TabViewWidthMode const& mode); + void OnCloseButtonOverlayModeChanged(winrt::TabViewCloseButtonOverlayMode const& mode); private: tracker_ref m_closeButton{ this }; tracker_ref m_toolTip{ this }; + winrt::TabViewWidthMode m_tabViewWidthMode{ winrt::TabViewWidthMode::Equal }; + winrt::TabViewCloseButtonOverlayMode m_closeButtonOverlayMode{ winrt::TabViewCloseButtonOverlayMode::Auto }; void UpdateCloseButton(); void RequestClose(); void OnIconSourceChanged(); + void UpdateWidthModeVisualState(); bool m_firstTimeSettingToolTip{ true }; @@ -63,6 +68,7 @@ class TabViewItem : bool m_hasPointerCapture = false; bool m_isMiddlePointerButtonPressed = false; bool m_isDragging = false; + bool m_isPointerOver = false; void UpdateShadow(); winrt::IInspectable m_shadow{ nullptr }; diff --git a/dev/TabView/TestUI/TabViewPage.xaml b/dev/TabView/TestUI/TabViewPage.xaml index 798379a988..90bed5bc07 100644 --- a/dev/TabView/TestUI/TabViewPage.xaml +++ b/dev/TabView/TestUI/TabViewPage.xaml @@ -26,11 +26,18 @@ Tab Width: - - + + + + + + + + + diff --git a/dev/TabView/TestUI/TabViewPage.xaml.cs b/dev/TabView/TestUI/TabViewPage.xaml.cs index a93cf2e039..47bb753dc6 100644 --- a/dev/TabView/TestUI/TabViewPage.xaml.cs +++ b/dev/TabView/TestUI/TabViewPage.xaml.cs @@ -191,8 +191,22 @@ private void TabWidthComboBox_SelectionChanged(object sender, SelectionChangedEv { switch (TabWidthComboBox.SelectedIndex) { - case 0: Tabs.TabWidthMode = Microsoft.UI.Xaml.Controls.TabViewWidthMode.SizeToContent; break; - case 1: Tabs.TabWidthMode = Microsoft.UI.Xaml.Controls.TabViewWidthMode.Equal; break; + case 0: Tabs.TabWidthMode = Microsoft.UI.Xaml.Controls.TabViewWidthMode.Equal; break; + case 1: Tabs.TabWidthMode = Microsoft.UI.Xaml.Controls.TabViewWidthMode.SizeToContent; break; + case 2: Tabs.TabWidthMode = Microsoft.UI.Xaml.Controls.TabViewWidthMode.Compact; break; + } + } + } + + private void CloseButtonOverlayModeCombobox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (Tabs != null) + { + switch (CloseButtonOverlayModeCombobox.SelectedIndex) + { + case 0: Tabs.CloseButtonOverlayMode = Microsoft.UI.Xaml.Controls.TabViewCloseButtonOverlayMode.Auto; break; + case 1: Tabs.CloseButtonOverlayMode = Microsoft.UI.Xaml.Controls.TabViewCloseButtonOverlayMode.OnHover; break; + case 2: Tabs.CloseButtonOverlayMode = Microsoft.UI.Xaml.Controls.TabViewCloseButtonOverlayMode.Always; break; } } } diff --git a/test/MUXControlsTestApp/MUXControlsTestApp.Shared.targets b/test/MUXControlsTestApp/MUXControlsTestApp.Shared.targets index 92c0a86281..780f1cfd19 100644 --- a/test/MUXControlsTestApp/MUXControlsTestApp.Shared.targets +++ b/test/MUXControlsTestApp/MUXControlsTestApp.Shared.targets @@ -71,6 +71,7 @@ +