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

fix: NavBar Icon fixes #589

Merged
merged 2 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
AddNavigationItems(nv);

// landing navigation
ShellNavigateTo<RuntimeTestRunner>(
ShellNavigateTo<NavigationBarSamplePage>(
#if WINDOWS_UWP
// note: on uwp, NavigationView.SelectedItem MUST be set on launch to avoid entering compact-mode
trySynchronizeCurrentItem: true
Expand Down Expand Up @@ -148,7 +148,7 @@

foreach (var category in categories.OrderBy(x => x.Key))
{
var tier = 1;

Check warning on line 151 in samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/App.xaml.Navigation.cs

View workflow job for this annotation

GitHub Actions / Build and Deploy Job

The variable 'tier' is assigned but its value is never used

Check warning on line 151 in samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/App.xaml.Navigation.cs

View workflow job for this annotation

GitHub Actions / Build and Deploy Job

The variable 'tier' is assigned but its value is never used

var parentItem = default(MUXC.NavigationViewItem);
if (category.Key != SampleCategory.None)
Expand Down
120 changes: 114 additions & 6 deletions src/Uno.Toolkit.RuntimeTests/Tests/NavigationBarTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,36 @@ public RedNavBarPage()
};
}
}

[TestMethod]
[RequiresFullWindow]
[DataRow(typeof(FontIconPage), DisplayName = nameof(FontIconPage))]
[DataRow(typeof(PathIconPage), DisplayName = nameof(PathIconPage))]
[DataRow(typeof(SymbolIconPage), DisplayName = nameof(SymbolIconPage))]
public async Task NavigationBar_Renders_With_Invalid_AppBarButton_IconElement(Type pageType)
{
var frame = new Frame { Width = 200, Height = 200 };

await UnitTestUIContentHelperEx.SetContentAndWait(frame);

var navBar = await frame.NavigateAndGetNavBar(pageType);
AssertNavigationBar(frame);
}


#if __ANDROID__
private static void AssertNavigationBar(Frame frame)
{
var page = frame.Content as Page;
var navBar = page?.FindChild<NavigationBar>();

var renderedNativeNavBar = navBar.GetNativeNavBar();

Assert.IsNotNull(renderedNativeNavBar);

Assert.IsTrue(renderedNativeNavBar!.Height > 0, "Native toolbar height is not greater than 0");
Assert.IsTrue(renderedNativeNavBar!.Width > 0, "Native toolbar width is not greater than 0");
}
#endif

#if __IOS__
Expand Down Expand Up @@ -372,9 +402,12 @@ private static void AssertNavigationBar(Frame frame)

Assert.AreSame(renderedNativeNavItem, presenter.NavigationController.TopViewController.NavigationItem);
Assert.AreSame(renderedNativeNavBar, presenter.NavigationController.NavigationBar);
}


Assert.IsTrue(renderedNativeNavBar!.Bounds.Height > 0, "Native toolbar height is not greater than 0");
Assert.IsTrue(renderedNativeNavBar!.Bounds.Width > 0, "Native toolbar width is not greater than 0");
}
#endif
#endif

private sealed partial class FirstPage : Page
{
Expand Down Expand Up @@ -428,7 +461,72 @@ public NavBarTestPage()
Content = PageContent;
}
}
#endif

private sealed partial class FontIconPage : Page
{
public FontIconPage()
{
var navBar = new NavigationBar
{
Content = "FontIconPage"
};

navBar.PrimaryCommands.Add(
new AppBarButton
{
Icon = new FontIcon
{
Glyph = "&#xE113;",
}
}
);

Content = navBar;
}
}

private sealed partial class SymbolIconPage : Page
{
public SymbolIconPage()
{
var navBar = new NavigationBar
{
Content = "SymbolIconPage"
};

navBar.PrimaryCommands.Add(
new AppBarButton
{
Icon = new SymbolIcon
{
Symbol = Symbol.Home,
}
}
);

Content = navBar;
}
}

private sealed partial class PathIconPage : Page
{
public PathIconPage()
{
var navBar = new NavigationBar
{
Content = "PathIconPage"
};

navBar.PrimaryCommands.Add(
new AppBarButton
{
Icon = new PathIcon(),
}
);

Content = navBar;
}
}
}

#if __IOS__ || __ANDROID__
Expand All @@ -442,13 +540,23 @@ public static class NavigationBarTestHelper
public static UINavigationItem? GetNativeNavItem(this NavigationBar? navBar) => navBar
?.TryGetRenderer<NavigationBar, NavigationBarNavigationItemRenderer>()
?.Native;

#elif __ANDROID__
public static AndroidX.AppCompat.Widget.Toolbar? GetNativeNavBar(this NavigationBar? navBar) => navBar
?.TryGetRenderer<NavigationBar, NavigationBarRenderer>()
?.Native;
#endif
public static async Task<NavigationBar?> NavigateAndGetNavBar<TPage>(this Frame frame) where TPage : Page
public static Task<NavigationBar?> NavigateAndGetNavBar<TPage>(this Frame frame) where TPage : Page
{
frame.Navigate(typeof(TPage));
return frame.NavigateAndGetNavBar(typeof(TPage));
}

public static async Task<NavigationBar?> NavigateAndGetNavBar(this Frame frame, Type pageType)
{
frame.Navigate(pageType);
await UnitTestsUIContentHelper.WaitForIdle();

var page = frame.Content as TPage;
var page = frame.Content as Page;
await UnitTestsUIContentHelper.WaitForLoaded(page!);
return page?.FindChild<NavigationBar>();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Uno.UI;
using Windows.Foundation;
using DrawableHelper = Uno.UI.DrawableHelper;
using static Uno.Toolkit.UI.VisualTreeHelperEx;
#if IS_WINUI
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
Expand Down Expand Up @@ -113,112 +114,69 @@ protected override IEnumerable<IDisposable> Initialize()

protected override void Render()
{
// CommandBar::PrimaryCommands -> !IsInOverflow -> AsAction.Never -> displayed directly on command bar
// CommandBar::SecondaryCommands -> IsInOverflow -> AsAction.Awalys -> (displayed as flyout menu items under [...])
// NavigationBar::PrimaryCommands -> !IsInOverflow -> AsAction.Never -> displayed directly on navbar
// NavigationBar::SecondaryCommands -> IsInOverflow -> AsAction.Awalys -> (displayed as flyout menu items under [...])

var native = Native;
var element = Element;
if (Native is not { } native)
{
return;
}

var element = Element ?? throw new InvalidOperationException("Element is null.");

// IsInOverflow
var showAsAction = _isInOverflow
? ShowAsAction.Never
: ShowAsAction.Always;
native?.SetShowAsAction(showAsAction);
native.SetShowAsAction(showAsAction);

string? titleText = element.Label;

string? titleText = null;
native.SetTitle(null);
native.SetActionView(null);
native.SetIcon(null);

// (Icon ?? Content) and Label
if (_isInOverflow)
if (!TrySetIcon() && element.Content is { } content)
{
native?.SetActionView(null);
native?.SetIcon(null);
native?.SetTitle(element?.Label);
}
else if (element?.Icon != null)
{
switch (element.Icon)
if (content is string text)
{
case BitmapIcon bitmap:
if (bitmap.UriSource is { } uriSource)
{
var drawable = DrawableHelper.FromUri(uriSource);
native?.SetIcon(drawable);
}
break;

case FontIcon font: // not supported
case PathIcon path: // not supported
case SymbolIcon symbol: // not supported
default:
this.Log().WarnIfEnabled(() => $"{GetType().Name ?? "FontIcon, PathIcon and SymbolIcon"} are not supported. Use BitmapIcon instead with UriSource.");
native?.SetIcon(null);
break;
titleText = text;
}
native?.SetActionView(null);
native?.SetTitle(null);
}
else if (element?.Content != null)
{
switch (element?.Content)
else if (content is FrameworkElement { Visibility: Visibility.Visible } fe &&
_appBarButtonWrapper is { } wrapper)
{
case string text:
native?.SetIcon(null);
native?.SetActionView(null);
//native?.SetTitle(text);
titleText = text;
break;

case FrameworkElement fe:
if (_appBarButtonWrapper is { })
{
_elementParent = element.Parent;
_appBarButtonWrapper.Child = element;
element.SetParent(_elementParent);
}
native?.SetIcon(null);
native?.SetActionView(fe.Visibility == Visibility.Visible ? _appBarButtonWrapper : null);
native?.SetTitle(null);
break;

default:
native?.SetIcon(null);
native?.SetActionView(null);
native?.SetTitle(null);
break;
_elementParent = element.Parent;
wrapper.Child = element;
element.SetParent(_elementParent);
native.SetActionView(wrapper);
}
}
else
{
native?.SetActionView(null);
native?.SetIcon(null);
//native?.SetTitle(element?.Label);
titleText = element?.Label;

}

// IsEnabled
native?.SetEnabled(element?.IsEnabled ?? false);
native.SetEnabled(element.IsEnabled);
// According to the Material Design guidelines, the opacity inactive icons should be:
// - Light background: 38%
// - Dark background: 50%
// Source: https://material.io/guidelines/style/icons.html
// For lack of a reliable way to identify whether the background is light or dark,
// we'll go with 50% opacity until this no longer satisfies projects requirements.
var isEnabledOpacity = (element?.IsEnabled ?? false ? 1.0 : 0.5);
var isEnabledOpacity = (element.IsEnabled ? 1.0 : 0.5);

// Visibility
native?.SetVisible(element?.Visibility == Visibility.Visible);
native.SetVisible(element.Visibility == Visibility.Visible);

// Foreground
var foreground = element?.Foreground as SolidColorBrush;
var foreground = element.Foreground as SolidColorBrush;
var foregroundColor = foreground?.Color;
var foregroundOpacity = foreground?.Opacity ?? 0;
var foregroundOpacity = 0d;

if (native?.Icon != null)
if (native.Icon != null)
{
if (foregroundColor != null)
if (element.TryGetIconColor(out var iconColor))
{
DrawableCompat.SetTint(native.Icon, (Android.Graphics.Color)foregroundColor);
foregroundOpacity = iconColor.A / 255d;
DrawableCompat.SetTint(native.Icon, (Android.Graphics.Color)iconColor);
}
else
{
Expand All @@ -232,16 +190,16 @@ protected override void Render()
{
var s = new SpannableString(titleText);
s.SetSpan(new ForegroundColorSpan((Android.Graphics.Color)foregroundColor), 0, titleText.Length, 0);
native?.SetTitle(s);
native.SetTitle(s);
}
else
{
native?.SetTitle(titleText);
native.SetTitle(titleText);
}
}

// Background
if (ColorHelper.TryGetColorWithOpacity(element?.Background, out var backgroundColor))
if (ColorHelper.TryGetColorWithOpacity(element.Background, out var backgroundColor))
{
_appBarButtonWrapper?.SetBackgroundColor((Android.Graphics.Color)backgroundColor);
}
Expand All @@ -252,9 +210,34 @@ protected override void Render()
var opacity = element.Opacity;
var finalOpacity = isEnabledOpacity * foregroundOpacity * opacity;
var alpha = (int)(finalOpacity * 255);
native?.Icon?.SetAlpha(alpha);
native.Icon?.SetAlpha(alpha);
}

bool TrySetIcon()
{
if (element?.Icon is { } icon)
{
if (icon is BitmapIcon bitmap)
{
if (bitmap.UriSource is { } uriSource)
{
var drawable = DrawableHelper.FromUri(uriSource);
native.SetIcon(drawable);
}

return true;
}
else
{
this.Log().WarnIfEnabled(() => $"{icon.GetType().Name ?? "FontIcon, PathIcon and SymbolIcon"} are not supported. Use BitmapIcon instead with UriSource.");
native.SetIcon(null);
}
}

return false;
}
}

}

internal partial class AppBarButtonWrapper : Border
Expand Down
Loading
Loading