Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add IndicatorPlacement to TabBar #540

Merged
merged 3 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added doc/assets/tabbar-indicator-placement-above.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/assets/tabbar-indicator-placement-below.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
97 changes: 74 additions & 23 deletions doc/controls/TabBarAndTabBarItem.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ SelectionIndicatorContent|object|Gets or sets the content to be displayed as the
SelectionIndicatorContentTemplate|DataTemplate|Gets or sets the data template that is used to display the content of the selection indicator
SelectionIndicatorPresenterStyle|Style|Gets or sets the style to be applied for the content of the `TabBarSelectionIndicatorPresenter`
SelectionIndicatorTransitionMode|IndicatorTransitionMode|Gets or sets the behavior of the selection indicator. The indicator can either slide or snap to the newly selected item. Defaults to `Snap`
SelectionIndicatorPlacement|IndicatorPlacement|Gets or sets the placement of the selection indicator. The indicator can either be displayed `Above` or `Below` the TabBarItem Content. Defaults to `Above`

> Note: `TabBar` only supports the single selection mode.

Expand Down Expand Up @@ -141,29 +142,29 @@ xmlns:utu="using:Uno.Toolkit.UI"
...

<utu:TabBar Style="{StaticResource MyCustomTabBarStyle}"
SelectedIndex="0">
<utu:TabBar.SelectionIndicatorContent>
<Border Height="2"
VerticalAlignment="Bottom"
Background="Red" />
</utu:TabBar.SelectionIndicatorContent>
<utu:TabBar.Items>
<utu:TabBarItem Content="HOME">
<utu:TabBarItem.Icon>
<SymbolIcon Symbol="Home" />
</utu:TabBarItem.Icon>
</utu:TabBarItem>
<utu:TabBarItem Content="SUPPORT">
<utu:TabBarItem.Icon>
<FontIcon Glyph="&#xE8F2;" />
</utu:TabBarItem.Icon>
</utu:TabBarItem>
<utu:TabBarItem Content="ABOUT">
<utu:TabBarItem.Icon>
<FontIcon Glyph="&#xE946;" />
</utu:TabBarItem.Icon>
</utu:TabBarItem>
</utu:TabBar.Items>
SelectedIndex="0">
<utu:TabBar.SelectionIndicatorContent>
<Border Height="2"
VerticalAlignment="Bottom"
Background="Red" />
</utu:TabBar.SelectionIndicatorContent>
<utu:TabBar.Items>
<utu:TabBarItem Content="HOME">
<utu:TabBarItem.Icon>
<SymbolIcon Symbol="Home" />
</utu:TabBarItem.Icon>
</utu:TabBarItem>
<utu:TabBarItem Content="SUPPORT">
<utu:TabBarItem.Icon>
<FontIcon Glyph="&#xE8F2;" />
</utu:TabBarItem.Icon>
</utu:TabBarItem>
<utu:TabBarItem Content="ABOUT">
<utu:TabBarItem.Icon>
<FontIcon Glyph="&#xE946;" />
</utu:TabBarItem.Icon>
</utu:TabBarItem>
</utu:TabBar.Items>
</utu:TabBar>
```

Expand All @@ -182,6 +183,56 @@ The selection indicator has two different transition modes:

![TabBar with slide selection](../assets/tabbar-selection-slide.gif)

### IndicatorPlacement

By default, the selection indicator is rendered above, on "on top" of, the actual content in the TabBar. For example, the following XAML will result in the screenshot below:

```xml
<utu:TabBar Margin="50"
Width="300"
SelectedIndex="1"
Background="Green"
VerticalAlignment="Center">
<utu:TabBar.Items>
<utu:TabBarItem Content="Tab 1" />
<utu:TabBarItem Content="Tab 2" />
<utu:TabBarItem Content="Tab 3" />
</utu:TabBar.Items>
<utu:TabBar.SelectionIndicatorContentTemplate>
<DataTemplate>
<Border Background="Red" />
</DataTemplate>
</utu:TabBar.SelectionIndicatorContentTemplate>
</utu:TabBar>
```

![](../assets/tabbar-indicator-placement-above.png)

The `SelectionIndicatorPlacement` property can be used to render the selection indicator "below" the content so it does not obscure the `TabBarItem`:

```xml
<utu:TabBar Margin="50"
Width="300"
SelectedIndex="1"
Background="Green"
SelectionIndicatorPlacement="Below"
VerticalAlignment="Center">
<utu:TabBar.Items>
<utu:TabBarItem Content="Tab 1" />
<utu:TabBarItem Content="Tab 2" />
<utu:TabBarItem Content="Tab 3" />
</utu:TabBar.Items>
<utu:TabBar.SelectionIndicatorContentTemplate>
<DataTemplate>
<Border Background="Red" />
</DataTemplate>
</utu:TabBar.SelectionIndicatorContentTemplate>
</utu:TabBar>
```

![](../assets/tabbar-indicator-placement-below.png)


### Further Customization

If further customization is required, such as custom animations when sliding the indicator, there is a `SelectionIndicatorPresenterStyle` property on `TabBar` that can be set to customize the style of the internal `ContentPresenter` that is used to display the selection indicator.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
<MaterialColorsV1 xmlns="using:Uno.Material"/>
<MaterialResourcesV1 xmlns="using:Uno.Material"/>
<MaterialToolkitTheme xmlns="using:Uno.Toolkit.UI.Material"
FontOverrideSource="ms-appx:///MaterialFontsOverride.xaml"
ColorOverrideSource="ms-appx:///ColorPaletteOverride.xaml" />

<!-- Load Uno.Cupertino resources -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using Microsoft.Extensions.Logging;
using VisualTreeHelperEx = Uno.Toolkit.Samples.Helpers.VisualTreeHelperEx;
using Uno.Toolkit.Samples.Content.NestedSamples;
using Uno.Toolkit.Samples.Content;

namespace Uno.Toolkit.Samples
{
Expand Down
186 changes: 185 additions & 1 deletion src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs
kazo0 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@
using Uno.UI.RuntimeTests;
using Uno.UI.Extensions;
using Windows.Foundation;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.UI;
using Windows.Foundation.Metadata;

#if IS_WINUI
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
#else
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml;
using Windows.UI;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
#endif

namespace Uno.Toolkit.RuntimeTests.Tests
Expand Down Expand Up @@ -261,5 +265,185 @@ public async Task Verify_Indicator_Transitions(Orientation orientation, Indicato
};
}
}

[TestMethod]
public async Task Verify_Indicator_Display_On_Selection()
{
if (!CanTakeScreenshot())
{
Assert.Inconclusive(); // System.NotImplementedException: RenderTargetBitmap is not supported on this platform.;
}

const int NumItems = 3;
var SUT = new TabBar
{
Background = new SolidColorBrush(Colors.Transparent),
SelectionIndicatorContentTemplate = XamlHelper.LoadXaml<DataTemplate>(@"
<DataTemplate>
<Border Background=""Red"" />
</DataTemplate>
"),
SelectionIndicatorPlacement = IndicatorPlacement.Above,
};

foreach (var item in Enumerable.Range(0, NumItems).Select(_ => CreateItem()))
{
SUT.Items.Add(item);
}

await UnitTestUIContentHelperEx.SetContentAndWait(SUT);



for (int i = 0; i < NumItems; i++)
{
SUT.SelectedIndex = i;
await UnitTestsUIContentHelper.WaitForIdle();

var selectedItem = SUT.ContainerFromItem(SUT.SelectedItem) as TabBarItem;
Assert.IsNotNull(selectedItem);

var renderer = await TakeScreenshot(SUT);
var centerPoint = selectedItem!.TransformToVisual(SUT).TransformPoint(new Point(selectedItem.ActualWidth / 2, selectedItem.ActualHeight / 2));
var pixel = await GetColorAt(renderer, (int)centerPoint.X, (int)centerPoint.Y);

AssertExpectedColor(Colors.Red, pixel);
kazo0 marked this conversation as resolved.
Show resolved Hide resolved

foreach (var nonSelected in SUT.Items.Cast<TabBarItem>().Where(x => !x.IsSelected))
{
centerPoint = nonSelected.TransformToVisual(SUT).TransformPoint(new Point(nonSelected.ActualWidth / 2, nonSelected.ActualHeight / 2));
pixel = await GetColorAt(renderer, (int)centerPoint.X, (int)centerPoint.Y);
AssertExpectedColor(Colors.Green, pixel);
}
}

TabBarItem CreateItem()
{
return new TabBarItem
{
Content = new Border
{
Padding = new Thickness(20),
Background = new SolidColorBrush(Colors.Green),
}
};
}
}

[TestMethod]
[DataRow(true, DisplayName = "Verify Indicator Above")]
kazo0 marked this conversation as resolved.
Show resolved Hide resolved
[DataRow(false, DisplayName = "Verify Indicator Below")]
public async Task Verify_Indicator_Placement(bool isAbove)
{
var item = new TabBarItem
{
Content = new Border
{
Padding = new Thickness(20),
Background = new SolidColorBrush(Colors.Green),
}
};

var SUT = new TabBar
{
Background = new SolidColorBrush(Colors.Transparent),
SelectionIndicatorContentTemplate = XamlHelper.LoadXaml<DataTemplate>(@"
<DataTemplate>
<Border Background=""Red"" />
</DataTemplate>
"),
SelectedIndex = 0,
SelectionIndicatorPlacement = isAbove ? IndicatorPlacement.Above : IndicatorPlacement.Below,
};
SUT.Items.Add(item);

await UnitTestUIContentHelperEx.SetContentAndWait(SUT);

var belowPresenter = VisualTreeHelperEx
.GetFirstDescendant<TabBarSelectionIndicatorPresenter>(SUT, x => x.Name == "BelowSelectionIndicatorPresenter");
var abovePresenter = VisualTreeHelperEx
.GetFirstDescendant<TabBarSelectionIndicatorPresenter>(SUT, x => x.Name == "AboveSelectionIndicatorPresenter");


var renderer = await TakeScreenshot(SUT);

var centerPoint = item.TransformToVisual(SUT).TransformPoint(new Point(item.ActualWidth / 2, item.ActualHeight / 2));
var pixel = await GetColorAt(renderer, (int)centerPoint.X, (int)centerPoint.Y);


if (isAbove)
{
Assert.AreEqual(Visibility.Collapsed, belowPresenter!.Visibility);
Assert.AreEqual(Visibility.Visible, abovePresenter!.Visibility);

if (pixel is { })
{
AssertExpectedColor(Colors.Red, pixel);
}
}
else
{
Assert.AreEqual(Visibility.Visible, belowPresenter!.Visibility);
Assert.AreEqual(Visibility.Collapsed, abovePresenter!.Visibility);

if (pixel is { })
{
AssertExpectedColor(Colors.Green, pixel);
}
}
}

private static void AssertExpectedColor(Color expected, Color? actual)
{
Assert.AreEqual(expected.A, actual?.A);
Assert.AreEqual(expected.R, actual?.R);
Assert.AreEqual(expected.G, actual?.G);
Assert.AreEqual(expected.B, actual?.B);
}
kazo0 marked this conversation as resolved.
Show resolved Hide resolved

private static async Task<Color?> GetColorAt(RenderTargetBitmap? bitmap, int x, int y)
{
if (bitmap is null)
{
return null;
}

var pixelBuffer = await bitmap.GetPixelsAsync();
var pixels = pixelBuffer.ToArray();

var offset = (y * bitmap.PixelWidth + x) * 4;
var a = pixels[offset + 3];
var r = pixels[offset + 2];
var g = pixels[offset + 1];
var b = pixels[offset + 0];

return Color.FromArgb(a, r, g, b);
}


private static async Task<RenderTargetBitmap?> TakeScreenshot(UIElement? element)
{
if (CanTakeScreenshot())
{
var renderer = new RenderTargetBitmap();
await renderer.RenderAsync(element);
return renderer;
}
else
{
return null;
}
}

private static bool CanTakeScreenshot()
kazo0 marked this conversation as resolved.
Show resolved Hide resolved
{
return ApiInformation.IsTypePresent(
#if IS_WINUI
"Microsoft.UI.Xaml.Media.Imaging.RenderTargetBitmap"
#else
"Windows.UI.Xaml.Media.Imaging.RenderTargetBitmap"
#endif
);
}
}
}
21 changes: 21 additions & 0 deletions src/Uno.Toolkit.UI/Controls/TabBar/IndicatorPlacement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Uno.Toolkit.UI
{
/// <summary>
/// Determines the placement of the selection indicator in a <see cref="TabBar"/>
/// </summary>
public enum IndicatorPlacement
{
/// <summary>
/// The selection indicator will be placed above the content of the <see cref="TabBar"/>
/// </summary>
Above,
/// <summary>
/// The selection indicator will be placed below, or "behind", the content of the <see cref="TabBar"/>
/// </summary>
Below,
}
}
15 changes: 15 additions & 0 deletions src/Uno.Toolkit.UI/Controls/TabBar/TabBar.Properties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,21 @@ public IndicatorTransitionMode SelectionIndicatorTransitionMode
set => SetValue(SelectionIndicatorTransitionModeProperty, value);
}

#endregion
#region DependencyProperty: SelectionIndicatorPlacement

public static DependencyProperty SelectionIndicatorPlacementProperty { get; } = DependencyProperty.Register(
nameof(SelectionIndicatorPlacement),
typeof(IndicatorPlacement),
typeof(TabBar),
new PropertyMetadata(IndicatorPlacement.Above, OnPropertyChanged));

public IndicatorPlacement SelectionIndicatorPlacement
{
get => (IndicatorPlacement)GetValue(SelectionIndicatorPlacementProperty);
set => SetValue(SelectionIndicatorPlacementProperty, value);
}

#endregion

private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
Expand Down
Loading