maui-linux/Xamarin.Forms.Platform.MacOS/NativeToolbarTracker.cs

542 строки
15 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AppKit;
using CoreGraphics;
using Xamarin.Forms.Internals;
using Xamarin.Forms.PlatformConfiguration.macOSSpecific;
namespace Xamarin.Forms.Platform.MacOS
{
class NativeToolbarGroup
{
public class Item
{
public NSToolbarItem ToolbarItem;
public NSButton Button;
public ToolbarItem Element;
}
public NativeToolbarGroup(NSToolbarItemGroup itemGroup, double minItemWidth = 0, double itemSpacing = 0)
{
Group = itemGroup;
Items = new List<Item>();
MinItemWidth = minItemWidth;
ItemSpacing = itemSpacing;
}
public NSToolbarItemGroup Group { get; }
public double MinItemWidth { get; }
public double ItemSpacing { get; }
public List<Item> Items { get; }
}
internal class NativeToolbarTracker : NSToolbarDelegate
{
const string ToolBarId = "AwesomeBarToolbar";
readonly string _defaultBackButtonTitle = "Back";
readonly ToolbarTracker _toolbarTracker;
NSToolbar _toolbar;
NavigationPage _navigation;
bool _hasTabs;
const double BackButtonItemWidth = 36;
const double ToolbarItemWidth = 44;
const double ToolbarItemHeight = 25;
const double ToolbarItemSpacing = 6;
const double ToolbarHeight = 30;
const double NavigationTitleMinSize = 300;
const string NavigationGroupIdentifier = "NavigationGroup";
const string TabbedGroupIdentifier = "TabbedGroup";
const string ToolbarItemsGroupIdentifier = "ToolbarGroup";
const string TitleGroupIdentifier = "TitleGroup";
NativeToolbarGroup _navigationGroup;
NativeToolbarGroup _tabbedGroup;
NativeToolbarGroup _toolbarGroup;
NativeToolbarGroup _titleGroup;
NSView _nsToolbarItemViewer;
public NativeToolbarTracker()
{
_toolbarTracker = new ToolbarTracker();
_toolbarTracker.CollectionChanged += ToolbarTrackerOnCollectionChanged;
}
public NavigationPage Navigation
{
get { return _navigation; }
set
{
if (_navigation == value)
return;
if (_navigation != null)
_navigation.PropertyChanged -= NavigationPagePropertyChanged;
_navigation = value;
if (_navigation != null)
{
var parentTabbedPage = _navigation.Parent as TabbedPage;
if (parentTabbedPage != null)
{
_hasTabs = parentTabbedPage.OnThisPlatform().GetTabsStyle() == TabsStyle.OnNavigation;
}
_toolbarTracker.Target = _navigation.CurrentPage;
_navigation.PropertyChanged += NavigationPagePropertyChanged;
}
UpdateToolBar();
}
}
public void TryHide(NavigationPage navPage = null)
{
if (navPage == null || navPage == _navigation)
{
Navigation = null;
}
}
public override string[] AllowedItemIdentifiers(NSToolbar toolbar)
{
return new string[] { };
}
public override string[] DefaultItemIdentifiers(NSToolbar toolbar)
{
return new string[] { };
}
public override NSToolbarItem WillInsertItem(NSToolbar toolbar, string itemIdentifier, bool willBeInserted)
{
var group = new NSToolbarItemGroup(itemIdentifier);
var view = new NSView();
group.View = view;
if (itemIdentifier == NavigationGroupIdentifier)
_navigationGroup = new NativeToolbarGroup(group,BackButtonItemWidth);
else if (itemIdentifier == TitleGroupIdentifier)
_titleGroup = new NativeToolbarGroup(group);
else if (itemIdentifier == TabbedGroupIdentifier)
_tabbedGroup = new NativeToolbarGroup(group);
else if (itemIdentifier == ToolbarItemsGroupIdentifier)
_toolbarGroup = new NativeToolbarGroup(group, ToolbarItemWidth, ToolbarItemSpacing);
return group;
}
protected virtual bool HasTabs => _hasTabs;
protected virtual NSToolbar ConfigureToolbar()
{
var toolbar = new NSToolbar(ToolBarId)
{
DisplayMode = NSToolbarDisplayMode.Icon,
AllowsUserCustomization = false,
ShowsBaselineSeparator = true,
SizeMode = NSToolbarSizeMode.Regular,
Delegate = this
};
if (Forms.IsMojaveOrNewer)
toolbar.CenteredItemIdentifier = TitleGroupIdentifier;
return toolbar;
}
internal void UpdateToolBar()
{
if (NSApplication.SharedApplication.MainWindow == null)
return;
if (_navigation == null)
{
if (_toolbar != null)
_toolbar.Visible = false;
_toolbar = null;
return;
}
var currentPage = _navigation.Peek(0);
if (NavigationPage.GetHasNavigationBar(currentPage))
{
if (_toolbar == null)
{
_toolbar = ConfigureToolbar();
NSApplication.SharedApplication.MainWindow.Toolbar = _toolbar;
_toolbar.InsertItem(NavigationGroupIdentifier, 0);
_toolbar.InsertItem(
HasTabs ? NSToolbar.NSToolbarSpaceItemIdentifier : NSToolbar.NSToolbarFlexibleSpaceItemIdentifier, 1);
_toolbar.InsertItem(HasTabs ? TabbedGroupIdentifier : TitleGroupIdentifier, 2);
_toolbar.InsertItem(NSToolbar.NSToolbarFlexibleSpaceItemIdentifier, 3);
_toolbar.InsertItem(ToolbarItemsGroupIdentifier, 4);
}
_toolbar.Visible = true;
UpdateToolbarItems();
UpdateTitle();
UpdateNavigationItems();
if (HasTabs)
UpdateTabbedItems();
UpdateBarBackgroundColor();
}
else
{
if (_toolbar != null)
{
_toolbar.Visible = false;
}
}
}
internal void UpdateNavigationItems(bool forceShowBackButton = false)
{
if (_toolbar == null || _navigation == null || _navigationGroup == null)
return;
var items = new List<ToolbarItem>();
if (ShowBackButton(forceShowBackButton))
{
bool isBackButtonTextSet = _navigation.IsSet(NavigationPage.BackButtonTitleProperty);
var backButtonTitle = isBackButtonTextSet ? NavigationPage.GetBackButtonTitle(_navigation) : GetPreviousPageTitle();
var backButtonItem = new ToolbarItem
{
Text = backButtonTitle,
Command = new Command(async () => await NavigateBackFrombackButton())
};
items.Add(backButtonItem);
}
UpdateGroup(_navigationGroup, items);
var navItemBack = _navigationGroup.Items.FirstOrDefault();
if (navItemBack != null)
{
navItemBack.Button.Image = NSImage.ImageNamed(NSImageName.GoLeftTemplate);
navItemBack.Button.SizeToFit();
UpdateGroupWidth(_navigationGroup);
navItemBack.Button.AccessibilityTitle = "NSBackButton";
}
}
void UpdateBarBackgroundColor()
{
var bgColor = GetBackgroundColor().CGColor;
if (_nsToolbarItemViewer?.Superview?.Superview == null ||
_nsToolbarItemViewer.Superview.Superview.Superview == null) return;
// NSTitlebarView
_nsToolbarItemViewer.Superview.Superview.Superview.WantsLayer = true;
_nsToolbarItemViewer.Superview.Superview.Superview.Layer.BackgroundColor = bgColor;
}
void NavigationPagePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals(NavigationPage.BarTextColorProperty.PropertyName) ||
e.PropertyName.Equals(NavigationPage.BarBackgroundColorProperty.PropertyName))
UpdateToolBar();
}
void ToolbarTrackerOnCollectionChanged(object sender, EventArgs eventArgs)
{
UpdateToolbarItems();
}
async Task NavigateBackFrombackButton()
{
var popAsyncInner = _navigation?.PopAsyncInner(true, true);
if (popAsyncInner != null)
await popAsyncInner;
}
bool ShowBackButton(bool forceShowBackButton)
{
if (_navigation == null)
return false;
return NavigationPage.GetHasBackButton(_navigation.CurrentPage) && (forceShowBackButton || !IsRootPage());
}
bool IsRootPage()
{
if (_navigation == null)
return true;
return _navigation.StackDepth <= 1;
}
NSColor GetBackgroundColor()
{
var backgroundNSColor = NSColor.Clear;
if (Navigation != null && Navigation.BarBackgroundColor != Color.Default)
backgroundNSColor = Navigation.BarBackgroundColor.ToNSColor();
return backgroundNSColor;
}
NSColor GetTitleColor()
{
var titleNSColor = NSColor.Black;
if (Navigation != null && Navigation?.BarTextColor != Color.Default)
titleNSColor = Navigation.BarTextColor.ToNSColor();
return titleNSColor;
}
string GetCurrentPageTitle()
{
if (_navigation == null)
return string.Empty;
return _navigation.Peek(0).Title ?? "";
}
string GetPreviousPageTitle()
{
if (_navigation == null || _navigation.StackDepth <= 1)
return string.Empty;
return _navigation.Peek(1).Title ?? _defaultBackButtonTitle;
}
List<ToolbarItem> GetToolbarItems()
{
return _toolbarTracker.ToolbarItems.ToList();
}
void UpdateTitle()
{
if (_toolbar == null || _navigation == null || _titleGroup == null)
return;
var title = GetCurrentPageTitle();
var item = new NSToolbarItem(title);
var view = new NSView();
var titleField = new NSTextField
{
AllowsEditingTextAttributes = true,
Bordered = false,
DrawsBackground = false,
Bezeled = false,
Editable = false,
Selectable = false,
Cell = new VerticallyCenteredTextFieldCell(0f, NSFont.TitleBarFontOfSize(18)),
StringValue = title
};
titleField.Cell.TextColor = GetTitleColor();
titleField.SizeToFit();
_titleGroup.Group.MinSize = new CGSize(NavigationTitleMinSize, ToolbarHeight);
_titleGroup.Group.Subitems = new NSToolbarItem[] { item };
view.AddSubview(titleField);
titleField.CenterXAnchor.ConstraintEqualToAnchor(view.CenterXAnchor).Active = true;
titleField.CenterYAnchor.ConstraintEqualToAnchor(view.CenterYAnchor).Active = true;
titleField.TranslatesAutoresizingMaskIntoConstraints = false;
view.TranslatesAutoresizingMaskIntoConstraints = false;
_titleGroup.Group.View = view;
//save a reference so we can paint this for the background
_nsToolbarItemViewer = _titleGroup.Group.View.Superview;
}
void UpdateToolbarItems()
{
if (_toolbar == null || _navigation == null || _toolbarGroup == null)
return;
var currentPage = _navigation.Peek(0);
_toolbarTracker.Target = currentPage;
UpdateGroup(_toolbarGroup, GetToolbarItems());
}
void UpdateTabbedItems()
{
if (_toolbar == null || _navigation == null || _tabbedGroup == null)
return;
var items = new List<ToolbarItem>();
var tabbedPage = _navigation.Parent as TabbedPage;
if (tabbedPage != null)
{
foreach (var item in tabbedPage.Children)
{
var tbI = new ToolbarItem
{
Text = item.Title,
IconImageSource = item.IconImageSource,
Command = new Command(() => tabbedPage.SelectedItem = item)
};
items.Add(tbI);
}
}
UpdateGroup(_tabbedGroup, items);
}
void UpdateGroup(NativeToolbarGroup group, IList<ToolbarItem> toolbarItems)
{
int count = toolbarItems.Count;
group.Items.Clear();
if (count > 0)
{
var subItems = new NSToolbarItem[count];
var view = new NSView();
for (int i = 0; i < toolbarItems.Count; i++)
{
var element = toolbarItems[i];
var item = new NSToolbarItem(element.Text ?? "");
item.Activated += (sender, e) => ((IMenuItemController)element).Activate();
var button = new NSButton();
// Padding for toolbar items with icon only is 9=44-25 (ToolbarItemWidth-ToolbarItemHeight)
// 10 added here to match this padding for long items
button.Activated += (sender, e) => ((IMenuItemController)element).Activate();
button.BezelStyle = NSBezelStyle.TexturedRounded;
UpdateButtonContent(button, element, group);
button.Enabled = item.Enabled = element.IsEnabled;
element.PropertyChanged -= ToolBarItemPropertyChanged;
element.PropertyChanged += ToolBarItemPropertyChanged;
view.AddSubview(button);
//item.Label = item.PaletteLabel = item.ToolTip = element.Text ?? "";
subItems[i] = item;
SetAccessibility(button, element);
group.Items.Add(new NativeToolbarGroup.Item { ToolbarItem = item, Button = button, Element = element });
}
group.Group.Subitems = subItems;
group.Group.View = view;
UpdateGroupWidth(group);
}
else
{
group.Group.Subitems = new NSToolbarItem[] { };
group.Group.View = new NSView();
}
}
void UpdateButtonContent(NSButton button, ToolbarItem element, NativeToolbarGroup group)
{
var text = element.Text ?? "";
SetButtonTitle(button, text);
button.SizeToFit();
UpdateGroupWidth(group);
if (element.IconImageSource != null)
{
_ = element.ApplyNativeImageAsync(ToolbarItem.IconImageSourceProperty, image =>
{
if (image != null)
{
button.Image = image;
SetButtonTitle(button, "");
}
else
{
button.Image = null;
}
button.SizeToFit();
UpdateGroupWidth(group);
});
}
else
{
button.Image = null;
}
}
void UpdateGroupWidth(NativeToolbarGroup group)
{
nfloat totalWidth = 0;
var currentX = 0.0;
var view = group.Group.View;
for (var i = 0; i < view.Subviews.Length; i++)
{
var child = view.Subviews[i];
var buttonWidth = group.MinItemWidth;
if (child.FittingSize.Width > group.MinItemWidth)
{
buttonWidth = child.FittingSize.Width + 10;
}
child.Frame = new CGRect(currentX + i * group.ItemSpacing, 0, buttonWidth, ToolbarItemHeight);
totalWidth += child.Frame.Width;
currentX += buttonWidth;
}
view.Frame = new CGRect(0, 0, totalWidth + (group.ItemSpacing * (view.Subviews.Length - 1)), ToolbarItemHeight);
}
void SetButtonTitle(NSButton button, string text)
{
button.Title = text;
button.ImagePosition = GetImagePosition(text);
}
NSCellImagePosition GetImagePosition(string text)
{
return string.IsNullOrEmpty(text) ? NSCellImagePosition.ImageOnly : NSCellImagePosition.ImageLeading;
}
void SetAccessibility(NSButton button, ToolbarItem element)
{
button.AccessibilityValue = element.IsSet(AutomationProperties.NameProperty)
? (Foundation.NSString)element.GetValue(AutomationProperties.NameProperty).ToString()
: null;
var titles = new List<string> { button.Title };
if (element.IsSet(AutomationProperties.HelpTextProperty))
titles.Add(element.GetValue(AutomationProperties.HelpTextProperty).ToString());
button.AccessibilityTitle = string.Join(", ", titles);
}
void ToolBarItemPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var nativeToolbarItem = _toolbarGroup.Items.FirstOrDefault((NativeToolbarGroup.Item arg1) => arg1.Element == sender);
if (nativeToolbarItem != null)
{
if (e.PropertyName.Equals(VisualElement.IsEnabledProperty.PropertyName))
{
nativeToolbarItem.Button.Enabled = nativeToolbarItem.ToolbarItem.Enabled = nativeToolbarItem.Element.IsEnabled;
}
else if (e.PropertyName.Equals(ToolbarItem.TextProperty.PropertyName))
{
var element = nativeToolbarItem.Element;
var button = nativeToolbarItem.Button;
var text = element.Text;
nativeToolbarItem.ToolbarItem.Label = text;
UpdateButtonContent(button, element, _toolbarGroup);
}
else if (e.PropertyName.Equals(ToolbarItem.IconImageSourceProperty.PropertyName))
{
var element = nativeToolbarItem.Element;
var button = nativeToolbarItem.Button;
UpdateButtonContent(button, element, _toolbarGroup);
}
else if (e.PropertyName == AutomationProperties.NameProperty.PropertyName ||
e.PropertyName == AutomationProperties.HelpTextProperty.PropertyName)
{
SetAccessibility(nativeToolbarItem.Button, nativeToolbarItem.Element);
}
}
}
class ToolBarItemNSButton : NSView
{
public ToolBarItemNSButton(string automationID)
{
AccessibilityIdentifier = automationID;
}
}
}
}