Skip to content

Commit

Permalink
Resize tab view items only once the pointer has left the TabViewItem …
Browse files Browse the repository at this point in the history
…strip (#2569)

* Add initial behavior

* Hide scroll buttons when they are not needed anymore

* Add test

* Remove comment

* Switch to tabstrip instead of listview for pointer exited event

* Resize tabs none the less if last item got removed

* Keep buttons visible but disabled, CR feedback

* Update behavior again, now aligned with edge

* Fix test failure

* Move pointer to different UI element in test
  • Loading branch information
marcelwgn committed Jun 5, 2020
1 parent eb6d418 commit 637b380
Show file tree
Hide file tree
Showing 8 changed files with 374 additions and 47 deletions.
105 changes: 105 additions & 0 deletions dev/TabView/InteractionTests/TabViewTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,14 @@ public void TabSizeAndScrollButtonsTest()
// Close a tab to make room. The scroll buttons should disappear:
Log.Comment("Closing a tab:");
Button closeButton = FindCloseButton(FindElement.ByName("LongHeaderTab"));
closeButton.MovePointer(0, 0);
closeButton.InvokeAndWait();
VerifyElement.NotFound("LongHeaderTab", FindBy.Name);

Log.Comment("Scroll buttons should disappear");
// Leaving tabstrip with this so the tabs update their width
FindElement.ByName<Button>("IsClosableCheckBox").MovePointer(0,0);
Wait.ForIdle();
Verify.IsFalse(AreScrollButtonsVisible(), "Scroll buttons should disappear");

// Make sure the scroll buttons can show up in 'Equal' sizing mode.
Expand Down Expand Up @@ -647,6 +651,107 @@ public void VerifyTabViewItemHeaderForegroundResource()
}
}

[TestMethod]
public void VerifySizingBehaviorOnTabCloseComingFromScroll()
{
int pixelTolerance = 10;

using (var setup = new TestSetupHelper(new[] { "TabView Tests", "TabViewTabClosingBehaviorButton" }))
{

Log.Comment("Verifying sizing behavior when closing a tab");
CloseTabAndVerifyWidth("Tab 1", 500, "True;False;");

CloseTabAndVerifyWidth("Tab 2", 500, "True;False;");

CloseTabAndVerifyWidth("Tab 3", 500, "False;False;");

CloseTabAndVerifyWidth("Tab 5", 401, "False;False;");

CloseTabAndVerifyWidth("Tab 4", 401, "False;False;");

Log.Comment("Leaving the pointer exited area");
var readTabViewWidthButton = new Button(FindElement.ByName("GetActualWidthButton"));
readTabViewWidthButton.Click();
Wait.ForIdle();

readTabViewWidthButton.Click();
Wait.ForIdle();

Log.Comment("Verify correct TabView width");
Verify.IsTrue(Math.Abs(GetActualTabViewWidth() - 283) < pixelTolerance);
}

void CloseTabAndVerifyWidth(string tabName, int expectedValue, string expectedScrollbuttonStates)
{
Log.Comment("Closing tab:" + tabName);
FindCloseButton(FindElement.ByName(tabName)).Click();
Wait.ForIdle();
Log.Comment("Verifying TabView width");
Verify.IsTrue(Math.Abs(GetActualTabViewWidth() - expectedValue) < pixelTolerance);
Verify.AreEqual(expectedScrollbuttonStates, FindElement.ByName("ScrollButtonStatus").GetText());

}

double GetActualTabViewWidth()
{
var tabviewWidth = new TextBlock(FindElement.ByName("TabViewWidth"));

return Double.Parse(tabviewWidth.GetText());
}
}

[TestMethod]
public void VerifySizingBehaviorOnTabCloseComingFromNonScroll()
{
int pixelTolerance = 10;

using (var setup = new TestSetupHelper(new[] { "TabView Tests", "TabViewTabClosingBehaviorButton" }))
{

Log.Comment("Verifying sizing behavior when closing a tab");
CloseTabAndVerifyWidth("Tab 1", 500, "True;False;");

CloseTabAndVerifyWidth("Tab 2", 500, "True;False;");

CloseTabAndVerifyWidth("Tab 3", 500, "False;False;");

var readTabViewWidthButton = new Button(FindElement.ByName("GetActualWidthButton"));
readTabViewWidthButton.Click();
Wait.ForIdle();

CloseTabAndVerifyWidth("Tab 5", 500, "False;False;");

CloseTabAndVerifyWidth("Tab 4", 500, "False;False;");

Log.Comment("Leaving the pointer exited area");

readTabViewWidthButton.Click();
Wait.ForIdle();

Log.Comment("Verify correct TabView width");
Verify.IsTrue(Math.Abs(GetActualTabViewWidth() - 500) < pixelTolerance);
}

void CloseTabAndVerifyWidth(string tabName, int expectedValue, string expectedScrollbuttonStates)
{
Log.Comment("Closing tab:" + tabName);
FindCloseButton(FindElement.ByName(tabName)).Click();
Wait.ForIdle();
Log.Comment("Verifying TabView width");
Verify.IsTrue(Math.Abs(GetActualTabViewWidth() - expectedValue) < pixelTolerance);
Verify.AreEqual(expectedScrollbuttonStates, FindElement.ByName("ScrollButtonStatus").GetText());

}

double GetActualTabViewWidth()
{
var tabviewWidth = new TextBlock(FindElement.ByName("TabViewWidth"));

return Double.Parse(tabviewWidth.GetText());
}
}

public void PressButtonAndVerifyText(String buttonName, String textBlockName, String expectedText)
{
Button button = FindElement.ByName<Button>(buttonName);
Expand Down
169 changes: 126 additions & 43 deletions dev/TabView/TabView.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

#include "pch.h"
Expand Down Expand Up @@ -80,7 +80,15 @@ void TabView::OnApplyTemplate()
m_addButtonColumn.set(GetTemplateChildT<winrt::ColumnDefinition>(L"AddButtonColumn", controlProtected));
m_rightContentColumn.set(GetTemplateChildT<winrt::ColumnDefinition>(L"RightContentColumn", controlProtected));

m_tabContainerGrid.set(GetTemplateChildT<winrt::Grid>(L"TabContainerGrid", controlProtected));
if (const auto& containerGrid = GetTemplateChildT<winrt::Grid>(L"TabContainerGrid", controlProtected))
{
m_tabContainerGrid.set(containerGrid);
m_tabStripPointerExitedRevoker = containerGrid.PointerExited(winrt::auto_revoke, { this,&TabView::OnTabStripPointerExited });
}
else
{
m_tabContainerGrid.set(nullptr);
}

m_shadowReceiver.set(GetTemplateChildT<winrt::Grid>(L"ShadowReceiver", controlProtected));

Expand Down Expand Up @@ -341,6 +349,18 @@ void TabView::OnListViewLoaded(const winrt::IInspectable&, const winrt::RoutedEv
}
}

void TabView::OnTabStripPointerExited(const winrt::IInspectable& sender, const winrt::PointerRoutedEventArgs& args)
{
if (updateTabWidthOnPointerLeave)
{
auto scopeGuard = gsl::finally([this]()
{
updateTabWidthOnPointerLeave = false;
});
UpdateTabWidths();
}
}

void TabView::OnScrollViewerLoaded(const winrt::IInspectable&, const winrt::RoutedEventArgs& args)
{
if (auto&& scrollViewer = m_scrollViewer.get())
Expand Down Expand Up @@ -423,8 +443,12 @@ void TabView::UpdateScrollViewerDecreaseAndIncreaseButtonsViewState()

void TabView::OnItemsPresenterSizeChanged(const winrt::IInspectable& sender, const winrt::SizeChangedEventArgs& args)
{
UpdateTabWidths();
UpdateScrollViewerDecreaseAndIncreaseButtonsViewState();
if (!updateTabWidthOnPointerLeave)
{
// Presenter size didn't change because of item being removed, so update manually
UpdateScrollViewerDecreaseAndIncreaseButtonsViewState();
UpdateTabWidths();
}
}

void TabView::OnItemsChanged(winrt::IInspectable const& item)
Expand All @@ -434,42 +458,61 @@ void TabView::OnItemsChanged(winrt::IInspectable const& item)
m_tabItemsChangedEventSource(*this, args);

int numItems = static_cast<int>(TabItems().Size());
if (args.CollectionChange() == winrt::CollectionChange::ItemRemoved && numItems > 0)

if (args.CollectionChange() == winrt::CollectionChange::ItemRemoved)
{
// SelectedIndex might also already be -1
auto selectedIndex = SelectedIndex();
if (selectedIndex == -1 || selectedIndex == static_cast<int32_t>(args.Index()))
updateTabWidthOnPointerLeave = true;
if (numItems > 0)
{
// Find the closest tab to select instead.
int startIndex = static_cast<int>(args.Index());
if (startIndex >= numItems)
// SelectedIndex might also already be -1
auto selectedIndex = SelectedIndex();
if (selectedIndex == -1 || selectedIndex == static_cast<int32_t>(args.Index()))
{
startIndex = numItems - 1;
}
int index = startIndex;

do
{
auto nextItem = ContainerFromIndex(index).as<winrt::ListViewItem>();

if (nextItem && nextItem.IsEnabled() && nextItem.Visibility() == winrt::Visibility::Visible)
// Find the closest tab to select instead.
int startIndex = static_cast<int>(args.Index());
if (startIndex >= numItems)
{
SelectedItem(TabItems().GetAt(index));
break;
startIndex = numItems - 1;
}
int index = startIndex;

// try the next item
index++;
if (index >= numItems)
do
{
index = 0;
}
} while (index != startIndex);
auto nextItem = ContainerFromIndex(index).as<winrt::ListViewItem>();

if (nextItem && nextItem.IsEnabled() && nextItem.Visibility() == winrt::Visibility::Visible)
{
SelectedItem(TabItems().GetAt(index));
break;
}

// try the next item
index++;
if (index >= numItems)
{
index = 0;
}
} while (index != startIndex);
}

}
// Last item removed, update sizes
// The index of the last element is "Size() - 1", but in TabItems, it is already removed.
if (TabWidthMode() == winrt::TabViewWidthMode::Equal)
{
updateTabWidthOnPointerLeave = true;
if (args.Index() == TabItems().Size())
{
UpdateTabWidths(true,false);
}
}
}
else
{
UpdateTabWidths();
}
}

UpdateTabWidths();
}

void TabView::OnListViewSelectionChanged(const winrt::IInspectable& sender, const winrt::SelectionChangedEventArgs& args)
Expand Down Expand Up @@ -620,6 +663,7 @@ void TabView::RequestCloseTab(winrt::TabViewItem const& container)
internalTabViewItem->RaiseRequestClose(*args);
}
}
UpdateTabWidths(false);
}

void TabView::OnScrollDecreaseClick(const winrt::IInspectable&, const winrt::RoutedEventArgs&)
Expand Down Expand Up @@ -649,7 +693,7 @@ winrt::Size TabView::MeasureOverride(winrt::Size const& availableSize)
return __super::MeasureOverride(availableSize);
}

void TabView::UpdateTabWidths()
void TabView::UpdateTabWidths(bool shouldUpdateWidths,bool fillAllAvailableSpace)
{
double tabWidth = std::numeric_limits<double>::quiet_NaN();

Expand Down Expand Up @@ -685,14 +729,41 @@ void TabView::UpdateTabWidths()
{
if (TabWidthMode() == winrt::TabViewWidthMode::Equal)
{

auto const minTabWidth = unbox_value<double>(SharedHelpers::FindInApplicationResources(c_tabViewItemMinWidthName, box_value(c_tabMinimumWidth)));
auto const maxTabWidth = unbox_value<double>(SharedHelpers::FindInApplicationResources(c_tabViewItemMaxWidthName, box_value(c_tabMaximumWidth)));

// Calculate the proportional width of each tab given the width of the ScrollViewer.
// If we should fill all of the available space, use scrollviewer dimensions
auto const padding = Padding();
auto const tabWidthForScroller = (availableWidth - (padding.Left + padding.Right)) / (double)(TabItems().Size());
if (fillAllAvailableSpace)
{
// Calculate the proportional width of each tab given the width of the ScrollViewer.
auto const tabWidthForScroller = (availableWidth - (padding.Left + padding.Right)) / (double)(TabItems().Size());
tabWidth = std::clamp(tabWidthForScroller, minTabWidth, maxTabWidth);
}
else
{
double availableTabViewSpace = (tabColumn.ActualWidth() - (padding.Left + padding.Right));
if (const auto increaseButton = m_scrollIncreaseButton.get())
{
if (increaseButton.Visibility() == winrt::Visibility::Visible)
{
availableTabViewSpace -= increaseButton.ActualWidth();
}
}

if (const auto decreaseButton = m_scrollDecreaseButton.get())
{
if (decreaseButton.Visibility() == winrt::Visibility::Visible)
{
availableTabViewSpace -= decreaseButton.ActualWidth();
}
}

// Use current size to update items to fill the currently occupied space
tabWidth = availableTabViewSpace / (double)(TabItems().Size());
}

tabWidth = std::clamp(tabWidthForScroller, minTabWidth, maxTabWidth);

// Size tab column to needed size
tabColumn.MaxWidth(availableWidth);
Expand All @@ -711,7 +782,15 @@ void TabView::UpdateTabWidths()
tabColumn.Width(winrt::GridLengthHelper::FromValueAndType(1.0, winrt::GridUnitType::Auto));
if (auto listview = m_listView.get())
{
winrt::FxScrollViewer::SetHorizontalScrollBarVisibility(listview, winrt::Windows::UI::Xaml::Controls::ScrollBarVisibility::Hidden);
if (shouldUpdateWidths && fillAllAvailableSpace)
{
winrt::FxScrollViewer::SetHorizontalScrollBarVisibility(listview, winrt::Windows::UI::Xaml::Controls::ScrollBarVisibility::Hidden);
}
else
{
m_scrollDecreaseButton.get().IsEnabled(false);
m_scrollIncreaseButton.get().IsEnabled(false);
}
}
}
}
Expand Down Expand Up @@ -742,18 +821,22 @@ void TabView::UpdateTabWidths()
}
}

for (auto item : TabItems())

if (shouldUpdateWidths || TabWidthMode() != winrt::TabViewWidthMode::Equal)
{
// Set the calculated width on each tab.
auto tvi = item.try_as<winrt::TabViewItem>();
if (!tvi)
for (auto item : TabItems())
{
tvi = ContainerFromItem(item).as<winrt::TabViewItem>();
}
// Set the calculated width on each tab.
auto tvi = item.try_as<winrt::TabViewItem>();
if (!tvi)
{
tvi = ContainerFromItem(item).as<winrt::TabViewItem>();
}

if (tvi)
{
tvi.Width(tabWidth);
if (tvi)
{
tvi.Width(tabWidth);
}
}
}
}
Expand Down
Loading

0 comments on commit 637b380

Please sign in to comment.