From f0ca6750108f88c27998df31478628b25ff32462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=EC=84=B1=ED=98=84/Common=20Platform=20Lab=28SR?= =?UTF-8?q?=29/Staff=20Engineer/=EC=82=BC=EC=84=B1=EC=A0=84=EC=9E=90?= Date: Mon, 23 Aug 2021 19:15:02 +0900 Subject: [PATCH] Add ShellHandler (#64) * Add ShellHandler * Move ShellView into Platform/Tizen * Update ShellView * Move the code for embedded tizen resources to csproj * Add UIExtenstions for shell --- .../src/Core/Controls.Core-net6.csproj | 6 + src/Controls/src/Core/Controls.Core.csproj | 5 + .../Core/Handlers/Shell/ShellHandler.Tizen.cs | 16 +- .../Core/Hosting/AppHostBuilderExtensions.cs | 4 +- .../Tizen/Extensions/ShellExtensions.cs | 117 +++++ .../Platform/Tizen/Resources/arrow_left.png | Bin 0 -> 490 bytes .../Tizen/Resources/dots_horizontal.png | Bin 0 -> 419 bytes .../Core/Platform/Tizen/Resources/menu.png | Bin 0 -> 417 bytes .../Tizen/Shell/ShellFlyoutItemAdaptor.cs | 170 +++++++ .../Platform/Tizen/Shell/ShellItemView.cs | 462 ++++++++++++++++++ .../Platform/Tizen/Shell/ShellMoreTabs.cs | 97 ++++ .../Core/Platform/Tizen/Shell/ShellNavBar.cs | 342 +++++++++++++ .../Platform/Tizen/Shell/ShellSectionStack.cs | 283 +++++++++++ .../Platform/Tizen/Shell/ShellSectionView.cs | 376 ++++++++++++++ .../Core/Platform/Tizen/Shell/ShellView.cs | 339 +++++++++++++ .../Platform/Tizen/Shell/SimpleViewStack.cs | 93 ++++ .../Tizen.UIExtensions/INavigationView.cs | 24 + .../Tizen/Shell/Tizen.UIExtensions/ITabs.cs | 30 ++ .../Tizen.UIExtensions/NavigationView.cs | 205 ++++++++ .../Tizen/Shell/Tizen.UIExtensions/Tabs.cs | 35 ++ .../Tizen.UIExtensions/ThemeConstants.cs | 25 + 21 files changed, 2623 insertions(+), 6 deletions(-) create mode 100644 src/Controls/src/Core/Platform/Tizen/Extensions/ShellExtensions.cs create mode 100644 src/Controls/src/Core/Platform/Tizen/Resources/arrow_left.png create mode 100644 src/Controls/src/Core/Platform/Tizen/Resources/dots_horizontal.png create mode 100644 src/Controls/src/Core/Platform/Tizen/Resources/menu.png create mode 100644 src/Controls/src/Core/Platform/Tizen/Shell/ShellFlyoutItemAdaptor.cs create mode 100644 src/Controls/src/Core/Platform/Tizen/Shell/ShellItemView.cs create mode 100644 src/Controls/src/Core/Platform/Tizen/Shell/ShellMoreTabs.cs create mode 100644 src/Controls/src/Core/Platform/Tizen/Shell/ShellNavBar.cs create mode 100644 src/Controls/src/Core/Platform/Tizen/Shell/ShellSectionStack.cs create mode 100644 src/Controls/src/Core/Platform/Tizen/Shell/ShellSectionView.cs create mode 100644 src/Controls/src/Core/Platform/Tizen/Shell/ShellView.cs create mode 100644 src/Controls/src/Core/Platform/Tizen/Shell/SimpleViewStack.cs create mode 100644 src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/INavigationView.cs create mode 100644 src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/ITabs.cs create mode 100644 src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/NavigationView.cs create mode 100644 src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/Tabs.cs create mode 100644 src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/ThemeConstants.cs diff --git a/src/Controls/src/Core/Controls.Core-net6.csproj b/src/Controls/src/Core/Controls.Core-net6.csproj index 75d5d3b9b772..41c4e7450445 100644 --- a/src/Controls/src/Core/Controls.Core-net6.csproj +++ b/src/Controls/src/Core/Controls.Core-net6.csproj @@ -7,6 +7,7 @@ false <_MauiDesignDllBuild Condition=" '$(OS)' != 'Unix' And '$(MSBuildRuntimeType)' == 'Full'">True high + Platform\Tizen\ @@ -29,6 +30,11 @@ + + + + + diff --git a/src/Controls/src/Core/Controls.Core.csproj b/src/Controls/src/Core/Controls.Core.csproj index 3b39e7060b90..f78b7ee73614 100644 --- a/src/Controls/src/Core/Controls.Core.csproj +++ b/src/Controls/src/Core/Controls.Core.csproj @@ -4,6 +4,7 @@ Microsoft.Maui.Controls Microsoft.Maui.Controls Platform\Android\ + Platform\Tizen\ Microsoft.Maui.Controls @@ -30,6 +31,10 @@ + + + + diff --git a/src/Controls/src/Core/Handlers/Shell/ShellHandler.Tizen.cs b/src/Controls/src/Core/Handlers/Shell/ShellHandler.Tizen.cs index cbd367a4a526..712d2ab78e9d 100644 --- a/src/Controls/src/Core/Handlers/Shell/ShellHandler.Tizen.cs +++ b/src/Controls/src/Core/Handlers/Shell/ShellHandler.Tizen.cs @@ -1,11 +1,19 @@ -using System; +using Microsoft.Maui.Controls.Platform; using Microsoft.Maui.Handlers; -using Tizen.UIExtensions.ElmSharp; namespace Microsoft.Maui.Controls.Handlers { - public partial class ShellHandler : ViewHandler + public partial class ShellHandler : ViewHandler { - protected override NavigationDrawer CreateNativeView() => new NavigationDrawer(NativeParent); + public override void SetVirtualView(IView view) + { + base.SetVirtualView(view); + NativeView.SetElement((Shell)view, MauiContext); + } + + protected override ShellView CreateNativeView() + { + return new ShellView(NativeParent); + } } } diff --git a/src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs b/src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs index 51d71c0ebf2f..0a6da92b3db6 100644 --- a/src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs +++ b/src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs @@ -20,7 +20,7 @@ public static partial class AppHostBuilderExtensions { typeof(CollectionView), typeof(CollectionViewHandler) }, #endif -#if WINDOWS || __ANDROID__ +#if WINDOWS || __ANDROID__ || TIZEN { typeof(Shell), typeof(ShellHandler) }, #endif { typeof(Application), typeof(ApplicationHandler) }, @@ -57,7 +57,7 @@ public static partial class AppHostBuilderExtensions { typeof(Window), typeof(WindowHandler) }, { typeof(ImageButton), typeof(ImageButtonHandler) }, { typeof(IndicatorView), typeof(IndicatorViewHandler) }, -#if __ANDROID__ || __IOS__ +#if __ANDROID__ || __IOS__ || TIZEN { typeof(RefreshView), typeof(RefreshViewHandler) }, #endif diff --git a/src/Controls/src/Core/Platform/Tizen/Extensions/ShellExtensions.cs b/src/Controls/src/Core/Platform/Tizen/Extensions/ShellExtensions.cs new file mode 100644 index 000000000000..d8e86a15b88e --- /dev/null +++ b/src/Controls/src/Core/Platform/Tizen/Extensions/ShellExtensions.cs @@ -0,0 +1,117 @@ +using Tizen.UIExtensions.Common; +using TINavigtaionView = Tizen.UIExtensions.Shell.INavigationView; + +namespace Microsoft.Maui.Controls.Platform +{ + public static class ShellExtensions + { + static double s_navigationViewFlyoutItemHeight = -1; + public static double GetFlyoutItemHeight(this TINavigtaionView nav) + { + if (s_navigationViewFlyoutItemHeight > 0) + return s_navigationViewFlyoutItemHeight; + return s_navigationViewFlyoutItemHeight = DeviceInfo.CalculateDoubleScaledSizeInLargeScreen(60); + } + + static double s_navigationViewFlyoutItemWidth = -1; + public static double GetFlyoutItemWidth(this TINavigtaionView nav) + { + if (s_navigationViewFlyoutItemWidth > 0) + return s_navigationViewFlyoutItemWidth; + return s_navigationViewFlyoutItemWidth = DeviceInfo.CalculateDoubleScaledSizeInLargeScreen(200); + } + + static double s_navigationViewFlyoutIconColumnSize = -1; + public static double GetFlyoutIconColumnSize(this TINavigtaionView nav) + { + if (s_navigationViewFlyoutIconColumnSize > 0) + return s_navigationViewFlyoutIconColumnSize; + return s_navigationViewFlyoutIconColumnSize = DeviceInfo.CalculateDoubleScaledSizeInLargeScreen(40); + } + + static double s_navigationViewFlyoutIconSize = -1; + public static double GetFlyoutIconSize(this TINavigtaionView nav) + { + if (s_navigationViewFlyoutIconSize > 0) + return s_navigationViewFlyoutIconSize; + return s_navigationViewFlyoutIconSize = DeviceInfo.CalculateDoubleScaledSizeInLargeScreen(25); + } + + static double s_navigationViewFlyoutMargin = -1; + public static double GetFlyoutMargin(this TINavigtaionView nav) + { + if (s_navigationViewFlyoutMargin > 0) + return s_navigationViewFlyoutMargin; + return s_navigationViewFlyoutMargin = DeviceInfo.CalculateDoubleScaledSizeInLargeScreen(10); + } + + static double s_navigationViewFlyoutItemFontSize = -1; + public static double GetFlyoutItemFontSize(this TINavigtaionView nav) + { + if (s_navigationViewFlyoutItemFontSize > 0) + return s_navigationViewFlyoutItemFontSize; + return s_navigationViewFlyoutItemFontSize = DeviceInfo.CalculateDoubleScaledSizeInLargeScreen(25); + } + + #region ShellMoreToolbar + + static double s_shellMoreToolBarIconPadding = -1; + public static double GetIconPadding(this ShellMoreTabs self) + { + if (s_shellMoreToolBarIconPadding > 0) + return s_shellMoreToolBarIconPadding; + return s_shellMoreToolBarIconPadding = DeviceInfo.CalculateDoubleScaledSizeInLargeScreen(15); + } + + static double s_shellMoreToolBarIconSize = -1; + public static double GetIconSize(this ShellMoreTabs self) + { + if (s_shellMoreToolBarIconSize > 0) + return s_shellMoreToolBarIconSize; + return s_shellMoreToolBarIconSize = DeviceInfo.CalculateDoubleScaledSizeInLargeScreen(30); + } + #endregion + + #region ShellNavBar + static double s_shellNavBarDefaultHeight = -1; + public static double GetDefaultHeight(this ShellNavBar navBar) + { + if (s_shellNavBarDefaultHeight > 0) + return s_shellNavBarDefaultHeight; + return s_shellNavBarDefaultHeight = DeviceInfo.CalculateDoubleScaledSizeInLargeScreen(70); + } + + static double s_shellNavBarDefaultMenuSize = -1; + public static double GetDefaultMenuSize(this ShellNavBar navBar) + { + if (s_shellNavBarDefaultMenuSize > 0) + return s_shellNavBarDefaultMenuSize; + return s_shellNavBarDefaultMenuSize = DeviceInfo.CalculateDoubleScaledSizeInLargeScreen(Device.Idiom == TargetIdiom.TV ? 70 : 40); + } + + static double s_shellNavBarDefaultMargin = -1; + public static double GetDefaultMargin(this ShellNavBar navBar) + { + if (s_shellNavBarDefaultMargin > 0) + return s_shellNavBarDefaultMargin; + return s_shellNavBarDefaultMargin = DeviceInfo.CalculateDoubleScaledSizeInLargeScreen(10); + } + + static double s_shellNavBarTitleVDefaultMargin = -1; + public static double GetDefaultTitleVMargin(this ShellNavBar navBar) + { + if (s_shellNavBarTitleVDefaultMargin > 0) + return s_shellNavBarTitleVDefaultMargin; + return s_shellNavBarTitleVDefaultMargin = DeviceInfo.CalculateDoubleScaledSizeInLargeScreen(23); + } + + static double s_shellNavBarTitleFontSize = -1; + public static double GetDefaultTitleFontSize(this ShellNavBar navBar) + { + if (s_shellNavBarTitleFontSize > 0) + return s_shellNavBarTitleFontSize; + return s_shellNavBarTitleFontSize = DeviceInfo.CalculateDoubleScaledSizeInLargeScreen(23); + } + #endregion + } +} diff --git a/src/Controls/src/Core/Platform/Tizen/Resources/arrow_left.png b/src/Controls/src/Core/Platform/Tizen/Resources/arrow_left.png new file mode 100644 index 0000000000000000000000000000000000000000..923dfeb34fef7636cd416788a5e63a0e782c54b9 GIT binary patch literal 490 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+eS5K)hhiu0R{01Y44~ zy9wy$!fk$L9koEv$x0Bg+K*nlM7srr@!*8$K^D#O~w0$ghaeB$4mmL1u zN=VndfR%Ua@l1g#AZBg%d{)IqvO18)$c9z>fY|)pqQz=^8N{xpsHQv z%kvz39g0e`{Wh?wRqfd{wXPslUi_7!$tjJ+ObZ^>9NIVI%9>E0)F8vU<~hYP&*>kV z_bY3$OEu8hswJ)wB`Jv|saDBFsX&Us$iUE2*T7iU$Rfnh$ja2f%Gg}nz`)ADK-@hP z6dVW*x%nxXX_dG&q)EMP0BX|k1|%Oc%$NbBSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+eS5K)hhiu0R{01Y44~ zy9wy$!fk$L9koEv$x0Bg+Kt_tEi(^Q{;kTCzxf%=vTrMU|+8nJAKfQsj&e~cXmcv2Erg#_GFs<>6xUFz`3Ul>!lrT4@f-?e^I zZ`)~H4$XMEWAD_R;m2m25{PcDoikCz^VbE&TR(+f`^(mWoTFOe8c~vxSdwa$T$Bo= z7>o=IEp-iyb&V`S42`TzO{@$Iv<(b^WN6D7Z4?c;`6-!cmAEyi+$@<6)Sv;fp|~vF zDk-rzRkyS#lOZiLC)G+{U%w=`KtDGzJu^95*V54?(BCLOE4y2A9Z(O0r>mdKI;Vst E0GM@v1^@s6 literal 0 HcmV?d00001 diff --git a/src/Controls/src/Core/Platform/Tizen/Resources/menu.png b/src/Controls/src/Core/Platform/Tizen/Resources/menu.png new file mode 100644 index 0000000000000000000000000000000000000000..6dcfc0575a1d6c10f34cf6df4933ce31b21ccf02 GIT binary patch literal 417 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+eS5K)hhiu0R{01Y44~ zy9wy$!fk$L9koEv$x0Bg+Kt__Mi(^Q{;kVZUxf&D%S{~kfk#J9^|Jm7P z*M&HpZnXS+UnrO^DKLeB;e1Z`^Od##zLheb)@M1>RhO{aL(B80uF|d24g7qfp6*GS z+gqnqe_Pyk>$bOAO4`HqKkC-(6JS_S%+#a$)V%xH<2;~kswJ)wB`Jv|saDBFsX&Us z$iUE2*T7iU$Rfnh$ja2%%Gglbz`)ADVE%&JAnzkI _nativeFormsTable = new Dictionary(); + Dictionary _dataBindedViewTable = new Dictionary(); + + Shell _shell; + View _headerCache; + IMauiContext _context; + + protected Shell Shell => _shell; + + public bool HasHeader { get; set; } + + protected virtual DataTemplate DefaultItemTemplate => null; + + protected virtual DataTemplate DefaultMenuItemTemplate => null; + + public ShellFlyoutItemAdaptor(Shell shell, IMauiContext context, IEnumerable items, bool hasHeader) : base(items) + { + _shell = shell; + _context = context; + HasHeader = hasHeader; + } + + public override EvasObject CreateNativeView(EvasObject parent) + { + return CreateNativeView(0, parent); + } + + DataTemplate GetDataTemplate(int index) + { + var item = (BindableObject)this[index]; + DataTemplate dataTemplate = (Shell as IShellController)?.GetFlyoutItemDataTemplate(item); + if (item is IMenuItemController) + { + if (DefaultMenuItemTemplate != null && Shell.MenuItemTemplate == dataTemplate) + dataTemplate = DefaultMenuItemTemplate; + } + else + { + if (DefaultItemTemplate != null && Shell.ItemTemplate == dataTemplate) + dataTemplate = DefaultItemTemplate; + } + + var template = dataTemplate.SelectDataTemplate(item, Shell); + + return template; + } + + public override EvasObject CreateNativeView(int index, EvasObject parent) + { + + var template = GetDataTemplate(index); + + if (template != null) + { + var content = (View)template.CreateContent(); + var native = content.ToNative(_context); + + _nativeFormsTable[native] = content; + return native; + } + + return null; + } + + public override EvasObject GetFooterView(EvasObject parent) + { + return null; + } + + public override EvasObject GetHeaderView(EvasObject parent) + { + if (!HasHeader) + return null; + + _headerCache = ((IShellController)Shell).FlyoutHeader; + + if (_headerCache != null) + { + var native = _headerCache.ToNative(_context); + return native; + } + + return null; + } + + public override Size MeasureFooter(int widthConstraint, int heightConstraint) + { + return new Size(0, 0); + } + + public override Size MeasureHeader(int widthConstraint, int heightConstraint) + { + return _headerCache?.Measure(DPExtensions.ConvertToScaledDP(widthConstraint), DPExtensions.ConvertToScaledDP(heightConstraint)).Request.ToEFLPixel() ?? new Size(0, 0); + } + + public override Size MeasureItem(int widthConstraint, int heightConstraint) + { + return MeasureItem(0, widthConstraint, heightConstraint); + } + + public override Size MeasureItem(int index, int widthConstraint, int heightConstraint) + { + if (_dataBindedViewTable.TryGetValue(this[index], out View createdView) && createdView != null) + { + return createdView.Measure(DPExtensions.ConvertToScaledDP(widthConstraint), DPExtensions.ConvertToScaledDP(heightConstraint), MeasureFlags.IncludeMargins).Request.ToEFLPixel(); + } + + return new Size(0, 0); + } + + public override void RemoveNativeView(EvasObject native) + { + native?.Unrealize(); + } + + public override void SetBinding(EvasObject native, int index) + { + if (_nativeFormsTable.TryGetValue(native, out View view)) + { + ResetBindedView(view); + view.BindingContext = this[index]; + _dataBindedViewTable[this[index]] = view; + + view.MeasureInvalidated += OnItemMeasureInvalidated; + Shell.AddLogicalChild(view); + } + } + + public override void UnBinding(EvasObject native) + { + if (_nativeFormsTable.TryGetValue(native, out View view)) + { + view.MeasureInvalidated -= OnItemMeasureInvalidated; + ResetBindedView(view); + } + } + + void ResetBindedView(View view) + { + if (view.BindingContext != null && _dataBindedViewTable.ContainsKey(view.BindingContext)) + { + _dataBindedViewTable[view.BindingContext] = null; + Shell.RemoveLogicalChild(view); + view.BindingContext = null; + } + } + + void OnItemMeasureInvalidated(object sender, EventArgs e) + { + var data = (sender as View)?.BindingContext ?? null; + int index = GetItemIndex(data); + if (index != -1) + { + CollectionView?.ItemMeasureInvalidated(index); + } + } + } +} diff --git a/src/Controls/src/Core/Platform/Tizen/Shell/ShellItemView.cs b/src/Controls/src/Core/Platform/Tizen/Shell/ShellItemView.cs new file mode 100644 index 000000000000..ac13803518c9 --- /dev/null +++ b/src/Controls/src/Core/Platform/Tizen/Shell/ShellItemView.cs @@ -0,0 +1,462 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Reflection; +using ElmSharp; +using Microsoft.Extensions.DependencyInjection; +using Tizen.UIExtensions.ElmSharp; +using Tizen.UIExtensions.Shell; +using EBox = ElmSharp.Box; +using EColor = ElmSharp.Color; +using EToolbarItem = ElmSharp.ToolbarItem; +using EToolbarItemEventArgs = ElmSharp.ToolbarItemEventArgs; +using TImage = Tizen.UIExtensions.ElmSharp.Image; +using TThemeConstants = Tizen.UIExtensions.Shell.ThemeConstants; + +namespace Microsoft.Maui.Controls.Platform +{ + public class ShellItemView : IAppearanceObserver, IDisposable + { + Tabs _tabs = null; + EBox _mainLayout = null; + EBox _contentHolder = null; + Panel _moreItemsDrawer = null; + ShellMoreTabs _moreItemsList = null; + EToolbarItem _moreTabItem = null; + ShellSectionStack _currentStack = null; + + Dictionary _sectionsTable = new Dictionary(); + Dictionary _tabItemsTable = new Dictionary(); + Dictionary _shellSectionStackCache = new Dictionary(); + List _tabsItems = new List(); + + bool _disposed = false; + Color _tabBarBackgroudColor = ShellView.DefaultBackgroundColor; + Color _tabBarTitleColor = ShellView.DefaultTitleColor; + + const string _dotsIcon = TThemeConstants.Shell.Resources.DotsIcon; + + public ShellItemView(ShellItem item, IMauiContext context) + { + ShellItem = item; + MauiContext = context; + + Initialize(); + + ShellItem.PropertyChanged += OnShellItemPropertyChanged; + if (ShellItem.Items is INotifyCollectionChanged notifyCollectionChanged) + { + notifyCollectionChanged.CollectionChanged += OnShellItemsCollectionChanged; + } + ShellController.AddAppearanceObserver(this, ShellItem); + + UpdateTabsItems(); + UpdateCurrentItem(ShellItem.CurrentItem); + } + + ~ShellItemView() + { + Dispose(false); + } + + protected IMauiContext MauiContext { get; private set; } + + protected EvasObject NativeParent + { + get => MauiContext?.Context?.BaseLayout; + } + + public EvasObject NativeView + { + get + { + return _mainLayout; + } + } + + public EColor TabBarBackgroundColor + { + get + { + return _tabBarBackgroudColor; + } + set + { + _tabBarBackgroudColor = value; + UpdateTabsBackgroudColor(_tabBarBackgroudColor); + } + } + + public EColor TabBarTitleColor + { + get => _tabBarTitleColor; + set + { + _tabBarTitleColor = value; + UpdateTabBarTitleColor(value); + } + } + + ShellItem ShellItem { get; } + IShellController ShellController => Shell.Current; + bool HasMoreItems => _moreItemsDrawer != null; + bool HasTabs => _tabs != null; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + ShellController.RemoveAppearanceObserver(this); + if (ShellItem != null) + { + ShellItem.PropertyChanged -= OnShellItemPropertyChanged; + if (ShellItem.Items is INotifyCollectionChanged notifyCollectionChanged) + { + notifyCollectionChanged.CollectionChanged -= OnShellItemsCollectionChanged; + } + + foreach (var stack in _shellSectionStackCache.Values) + { + stack.Dispose(); + } + + DestroyMoreItems(); + DeinitializeTabs(); + + _sectionsTable.Clear(); + _tabItemsTable.Clear(); + _shellSectionStackCache.Clear(); + _tabsItems.Clear(); + } + NativeView.Unrealize(); + } + _disposed = true; + } + + protected virtual void UpdateTabsItems() + { + ResetTabs(); + if (ShellItem.Items.Count > 1) + { + InitializeTabs(); + foreach (ShellSection section in ShellItem.Items) + { + AddTabsItem(section); + } + } + else + { + DeinitializeTabs(); + } + } + + protected virtual ShellSectionStack CreateShellSectionStack(ShellSection section) + { + return new ShellSectionStack(section, MauiContext); + } + + bool _disableMoreItemOpen; + + void UpdateCurrentItem(ShellSection section) + { + UpdateCurrentShellSection(section); + + if (HasTabs) + { + if (_tabItemsTable.ContainsKey(section)) + { + _tabItemsTable[section].IsSelected = true; + } + else if (HasMoreItems) + { + _disableMoreItemOpen = true; + _moreTabItem.IsSelected = true; + _disableMoreItemOpen = false; + } + + if (HasMoreItems) + { + _moreItemsDrawer.IsOpen = false; + } + } + } + + void UpdateCurrentItemFromUI(ShellSection section) + { + if (ShellItem.CurrentItem != section) + { + ShellItem.SetValueFromRenderer(ShellItem.CurrentItemProperty, section); + } + if (HasMoreItems) + { + _moreItemsDrawer.IsOpen = false; + } + } + + void Initialize() + { + _mainLayout = new EBox(NativeParent); + _mainLayout.SetLayoutCallback(OnLayout); + _mainLayout.Show(); + _contentHolder = new EBox(NativeParent); + _contentHolder.Show(); + _mainLayout.PackEnd(_contentHolder); + } + + void InitializeTabs() + { + if (_tabs != null) + return; + + _tabs = new Tabs(NativeParent); + _tabs.Show(); + _tabs.BackgroundColor = _tabBarBackgroudColor; + _tabs.Scrollable = TabsType.Fixed; + _tabs.Selected += OnTabsSelected; + _mainLayout.PackEnd(_tabs); + } + + void DeinitializeTabs() + { + if (_tabs == null) + return; + + _mainLayout.UnPack(_tabs); + _tabs.Selected -= OnTabsSelected; + _tabs.Unrealize(); + _tabs = null; + } + + void CreateMoreItems() + { + if (_moreItemsDrawer != null) + return; + + _moreItemsList = new ShellMoreTabs(NativeParent); + _moreItemsList.Show(); + _moreItemsList.ItemSelected += OnMoreItemSelected; + + _moreItemsDrawer = new Panel(NativeParent); + _moreItemsDrawer.Show(); + + _moreItemsDrawer.SetScrollable(true); + _moreItemsDrawer.SetScrollableArea(1.0); + _moreItemsDrawer.Direction = PanelDirection.Bottom; + _moreItemsDrawer.IsOpen = false; + _moreItemsDrawer.SetContent(_moreItemsList, true); + _mainLayout.PackEnd(_moreItemsDrawer); + } + + void DestroyMoreItems() + { + if (_moreItemsDrawer == null) + return; + + _mainLayout.UnPack(_moreItemsDrawer); + + _moreItemsList.Unrealize(); + _moreItemsDrawer.Unrealize(); + + _moreItemsList = null; + _moreItemsDrawer = null; + } + + void OnMoreItemSelected(object sender, GenListItemEventArgs e) + { + ShellSection section = e.Item.Data as ShellSection; + UpdateCurrentItemFromUI(section); + } + + void OnShellItemPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ShellItem.CurrentItem)) + { + UpdateCurrentItem(ShellItem.CurrentItem); + } + } + + void IAppearanceObserver.OnAppearanceChanged(ShellAppearance appearance) + { + var tabBarBackgroudColor = (appearance as IShellAppearanceElement)?.EffectiveTabBarBackgroundColor; + var tabBarTitleColor = (appearance as IShellAppearanceElement)?.EffectiveTabBarTitleColor; + TabBarBackgroundColor = tabBarBackgroudColor.IsDefault() ? ShellView.DefaultBackgroundColor : tabBarBackgroudColor.ToNativeEFL(); + TabBarTitleColor = tabBarTitleColor.IsDefault() ? ShellView.DefaultTitleColor : tabBarTitleColor.ToNativeEFL(); + } + + void UpdateTabsBackgroudColor(EColor color) + { + foreach (EToolbarItem item in _tabsItems) + { + item.SetBackgroundColor(color); + } + } + + void UpdateTabBarTitleColor(EColor color) + { + foreach (EToolbarItem item in _tabsItems) + { + item.SetTextColor(color); + } + } + + void ResetTabs() + { + if (!HasTabs) + return; + + foreach (var item in _tabsItems) + { + item.Delete(); + } + _tabsItems.Clear(); + DestroyMoreItems(); + _moreTabItem = null; + } + + void OnShellItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + UpdateTabsItems(); + } + + void AddTabsItem(ShellSection section) + { + if (_tabsItems.Count < 5) + { + var item = AppendTabsItem(section.Title, section.Icon); + _sectionsTable.Add(item, section); + _tabItemsTable.Add(section, item); + _tabsItems.Add(item); + } + else if (!HasMoreItems) + { + CreateMoreItems(); + + var last = _tabsItems.Last(); + var lastSection = _sectionsTable[last]; + + _tabsItems.Remove(last); + _sectionsTable.Remove(last); + _tabItemsTable.Remove(lastSection); + last.Delete(); + + //The source of icon resources is https://materialdesignicons.com/ + var assembly = typeof(ShellItemView).GetTypeInfo().Assembly; + var assemblyName = assembly.GetName().Name; + _moreTabItem = AppendTabsItem("More", ImageSource.FromResource(assemblyName + "." + _dotsIcon, assembly)); + _tabsItems.Add(_moreTabItem); + + _moreItemsList.AddItem(lastSection); + _moreItemsList.AddItem(section); + } + else + { + _moreItemsList.AddItem(section); + } + } + + void UpdateCurrentShellSection(ShellSection section) + { + if (_currentStack != null) + { + _currentStack.Hide(); + _contentHolder.UnPack(_currentStack); + } + _currentStack = null; + + if (section == null) + { + return; + } + + ShellSectionStack native; + if (_shellSectionStackCache.ContainsKey(section)) + { + native = _shellSectionStackCache[section]; + } + else + { + native = CreateShellSectionStack(section); + _shellSectionStackCache[section] = native; + } + _currentStack = native; + _currentStack.Show(); + _contentHolder.PackEnd(_currentStack); + } + + void OnTabsSelected(object sender, EToolbarItemEventArgs e) + { + if (_tabs.SelectedItem == null) + return; + + if (HasMoreItems && e.Item == _moreTabItem) + { + if (!_disableMoreItemOpen) + { + _moreItemsDrawer.IsOpen = !_moreItemsDrawer.IsOpen; + } + } + else + { + UpdateCurrentItemFromUI(_sectionsTable[_tabs.SelectedItem]); + } + } + + void OnLayout() + { + if (NativeView.Geometry.Height == 0 || NativeView.Geometry.Width == 0) + return; + + int tabsHeight = 0; + var bound = _mainLayout.Geometry; + if (HasTabs) + { + tabsHeight = _tabs.MinimumHeight; + var tabsBound = bound; + tabsBound.Y += (bound.Height - tabsHeight); + tabsBound.Height = tabsHeight; + _tabs.Geometry = tabsBound; + if (HasMoreItems) + { + int moreItemListHeight = _moreItemsList.HeightRequest; + moreItemListHeight = Math.Min(moreItemListHeight, bound.Height - tabsHeight); + var moreItemDrawerBound = bound; + moreItemDrawerBound.Y += (bound.Height - tabsHeight - moreItemListHeight); + moreItemDrawerBound.Height = moreItemListHeight; + _moreItemsDrawer.Geometry = moreItemDrawerBound; + } + } + bound.Height -= tabsHeight; + _contentHolder.Geometry = bound; + } + + EToolbarItem AppendTabsItem(string text, ImageSource iconSource) + { + var item = _tabs.Append(text); + if (iconSource != null) + { + TImage image = new TImage(NativeParent); + var provider = MauiContext.Services.GetRequiredService(); + var service = provider.GetRequiredImageSourceService(iconSource); + + _ = service.LoadImageAsync(iconSource, image); + + item.SetIconPart(image); + } + item.SetBackgroundColor(_tabBarBackgroudColor); + item.SetUnderlineColor(EColor.Transparent); + item.SetTextColor(_tabBarTitleColor); + + return item; + } + } +} diff --git a/src/Controls/src/Core/Platform/Tizen/Shell/ShellMoreTabs.cs b/src/Controls/src/Core/Platform/Tizen/Shell/ShellMoreTabs.cs new file mode 100644 index 000000000000..b532731b4cfb --- /dev/null +++ b/src/Controls/src/Core/Platform/Tizen/Shell/ShellMoreTabs.cs @@ -0,0 +1,97 @@ +using ElmSharp; +using Tizen.UIExtensions.ElmSharp; +using EBox = ElmSharp.Box; +using EImage = ElmSharp.Image; +using TImage = Tizen.UIExtensions.ElmSharp.Image; +using TLabel = Tizen.UIExtensions.ElmSharp.Label; + +namespace Microsoft.Maui.Controls.Platform +{ + public class ShellMoreTabs : GenList + { + GenItemClass _defaultClass = null; + + public ShellMoreTabs(EvasObject parent) : base(parent) + { + SetAlignment(-1, -1); + SetWeight(1, 1); + NativeParent = parent; + + Homogeneous = true; + SelectionMode = GenItemSelectionMode.Always; + BackgroundColor = ShellView.DefaultBackgroundColor; + _defaultClass = new GenItemClass(ThemeConstants.GenItemClass.Styles.Full) + { + GetContentHandler = GetContent, + }; + } + + protected EvasObject NativeParent { get; private set; } + + public void AddItem(ShellSection section) + { + Append(_defaultClass, section); + } + + public int HeightRequest + { + get + { + var cellHeight = this.GetIconSize() * 2 + this.GetIconSize(); + return DPExtensions.ConvertToScaledPixel(cellHeight) * Count; + } + } + + EvasObject GetContent(object data, string part) + { + ShellSection section = data as ShellSection; + + var box = new EBox(NativeParent); + box.Show(); + + EImage icon = null; + if (section.Icon != null) + { + icon = new TImage(NativeParent); + icon.Show(); + box.PackEnd(icon); + } + + var title = new TLabel(NativeParent) + { + Text = section.Title, + FontSize = DPExtensions.ConvertToEflFontPoint(14), + HorizontalTextAlignment = Tizen.UIExtensions.Common.TextAlignment.Start, + VerticalTextAlignment = Tizen.UIExtensions.Common.TextAlignment.Center, + }; + title.Show(); + box.PackEnd(title); + int iconPadding = DPExtensions.ConvertToScaledPixel(this.GetIconPadding()); + int iconSize = DPExtensions.ConvertToScaledPixel(this.GetIconSize()); + int cellHeight = iconPadding * 2 + iconSize; + box.SetLayoutCallback(() => + { + var bound = box.Geometry; + int leftMargin = iconPadding; + + if (icon != null) + { + var iconBound = bound; + iconBound.X += iconPadding; + iconBound.Y += iconPadding; + iconBound.Width = iconSize; + iconBound.Height = iconSize; + icon.Geometry = iconBound; + leftMargin = (2 * iconPadding + iconSize); + } + + bound.X += leftMargin; + bound.Width -= leftMargin; + title.Geometry = bound; + }); + + box.MinimumHeight = cellHeight; + return box; + } + } +} diff --git a/src/Controls/src/Core/Platform/Tizen/Shell/ShellNavBar.cs b/src/Controls/src/Core/Platform/Tizen/Shell/ShellNavBar.cs new file mode 100644 index 000000000000..4752f43f7ce4 --- /dev/null +++ b/src/Controls/src/Core/Platform/Tizen/Shell/ShellNavBar.cs @@ -0,0 +1,342 @@ +using System; +using System.Reflection; +using ElmSharp; +using Microsoft.Extensions.DependencyInjection; +using Tizen.UIExtensions.ElmSharp; +using EBox = ElmSharp.Box; +using EColor = ElmSharp.Color; +using TButton = Tizen.UIExtensions.ElmSharp.Button; +using TImage = Tizen.UIExtensions.ElmSharp.Image; +using TLabel = Tizen.UIExtensions.ElmSharp.Label; +using TThemeConstants = Tizen.UIExtensions.Shell.ThemeConstants; + +namespace Microsoft.Maui.Controls.Platform +{ + public class ShellNavBar : EBox, IFlyoutBehaviorObserver, IDisposable + { + TImage _menuIcon = null; + TButton _menuButton = null; + TLabel _title = null; + EvasObject _nativeTitleView = null; + View _titleView = null; + Page _page = null; + + FlyoutBehavior _flyoutBehavior = FlyoutBehavior.Flyout; + + EColor _backgroudColor = ShellView.DefaultBackgroundColor; + EColor _foregroudColor = ShellView.DefaultForegroundColor; + EColor _titleColor = ShellView.DefaultTitleColor; + + // The source of icon resources is https://materialdesignicons.com/ + const string _menuIconRes = TThemeConstants.Shell.Resources.MenuIcon; + const string _backIconRes = TThemeConstants.Shell.Resources.BackIcon; + + bool _hasBackButton = false; + private bool disposedValue; + bool _isTV = Device.Idiom == TargetIdiom.TV; + + public ShellNavBar(IMauiContext context) : base(context?.Context?.BaseLayout) + { + MauiContext = context; + + SetLayoutCallback(OnLayout); + + _menuButton = new TButton(NativeParent); + _menuButton.Clicked += OnMenuClicked; + + _menuIcon = new TImage(NativeParent); + UpdateMenuIcon(); + + _title = new TLabel(NativeParent) + { + FontSize = this.GetDefaultTitleFontSize(), + VerticalTextAlignment = (global::Tizen.UIExtensions.Common.TextAlignment)TextAlignment.Center, + TextColor = _titleColor.ToCommon(), + FontAttributes = Tizen.UIExtensions.Common.FontAttributes.Bold, + }; + _title.Show(); + + BackgroundColor = _backgroudColor; + _menuButton.BackgroundColor = _backgroudColor; + PackEnd(_menuButton); + PackEnd(_title); + } + + ~ShellNavBar() + { + Dispose(false); + } + + protected IMauiContext MauiContext { get; private set; } + + protected EvasObject NativeParent + { + get => MauiContext?.Context?.BaseLayout; + } + + public IShellController ShellController => Shell.Current; + + public bool HasBackButton + { + get + { + return _hasBackButton; + } + set + { + _hasBackButton = value; + UpdateMenuIcon(); + } + } + + public FlyoutBehavior FlyoutBehavior + { + get => _flyoutBehavior; + set + { + if (_flyoutBehavior != value) + { + _flyoutBehavior = value; + UpdateMenuIcon(); + } + } + } + + public View TitleView + { + get + { + return _titleView; + } + set + { + _titleView = value; + UpdateTitleView(_titleView); + UpdateChildren(); + } + } + + public string Title + { + get + { + return _title?.Text; + } + set + { + _title.Text = value; + } + } + + public override EColor BackgroundColor + { + get + { + return _backgroudColor; + } + set + { + _backgroudColor = value; + _menuButton.BackgroundColor = _backgroudColor; + base.BackgroundColor = _backgroudColor; + } + } + + public EColor ForegroundColor + { + get + { + return _foregroudColor; + } + set + { + _foregroudColor = value; + } + } + + public EColor TitleColor + { + get + { + return _titleColor; + } + set + { + _titleColor = value; + _title.TextColor = value.ToCommon(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void SetPage(Page page) + { + _page = page; + Title = page.Title; + //SearchHandler = Shell.GetSearchHandler(page); + TitleView = Shell.GetTitleView(page); + UpdateMenuIcon(); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + Unrealize(); + } + disposedValue = true; + } + } + + async void UpdateMenuIcon() + { + ImageSource source = null; + if (HasBackButton) + { + if (_isTV) + { + _menuButton.Style = ThemeConstants.Button.Styles.Default; + _menuButton.Text = ThemeConstants.Shell.Resources.TV.BackIconCode; + _menuIcon = null; + } + else + { + var assembly = typeof(ShellNavBar).GetTypeInfo().Assembly; + var assemblyName = assembly.GetName().Name; + source = ImageSource.FromResource(assemblyName + "." + _backIconRes, assembly); + } + } + else if (_flyoutBehavior != FlyoutBehavior.Flyout) + { + _menuButton.Hide(); + } + else if (ShellController.FlyoutIcon != null) + { + if (_isTV) + { + _menuButton.Style = ThemeConstants.Button.Styles.Circle; + _menuIcon = new TImage(NativeParent); + } + source = Shell.Current.FlyoutIcon; + } + else + { + if (_isTV) + { + _menuButton.Style = ThemeConstants.Button.Styles.Default; + _menuButton.Text = ThemeConstants.Shell.Resources.TV.MenuIconCode; + _menuIcon = null; + } + else + { + var assembly = typeof(ShellNavBar).GetTypeInfo().Assembly; + var assemblyName = assembly.GetName().Name; + source = ImageSource.FromResource(assemblyName + "." + _menuIconRes, assembly); + + } + } + + if (source != null && _menuIcon != null) + { + _menuIcon.Show(); + var provider = MauiContext.Services.GetRequiredService(); + var service = provider.GetRequiredImageSourceService(source); + + await service.LoadImageAsync(source, _menuIcon); + } + _menuButton.SetIconPart(_menuIcon); + _menuButton.Show(); + } + + void OnMenuClicked(object sender, EventArgs e) + { + var backButtonHandler = Shell.GetBackButtonBehavior(_page); + if (backButtonHandler?.Command != null) + { + backButtonHandler.Command.Execute(backButtonHandler.CommandParameter); + } + else if (_hasBackButton) + { + Shell.Current.CurrentItem.Navigation.PopAsync(); + } + else if (_flyoutBehavior == FlyoutBehavior.Flyout) + { + Shell.Current.FlyoutIsPresented = !Shell.Current.FlyoutIsPresented; + } + } + + void UpdateTitleView(View titleView) + { + _nativeTitleView?.Unrealize(); + _nativeTitleView = null; + + if (titleView != null) + { + var _nativeTitleView = titleView.ToNative(MauiContext); + _nativeTitleView.Show(); + PackEnd(_nativeTitleView); + } + } + + void UpdateChildren() + { + if (_titleView != null) + { + _nativeTitleView.Show(); + _title?.Hide(); + } + else + { + _title.Show(); + _nativeTitleView?.Hide(); + } + } + + void OnLayout() + { + if (Geometry.Width == 0 || Geometry.Height == 0) + return; + + int menuSize = DPExtensions.ConvertToScaledPixel(this.GetDefaultMenuSize()); + int menuMargin = DPExtensions.ConvertToScaledPixel(this.GetDefaultMargin()); + int titleHMargin = DPExtensions.ConvertToScaledPixel(this.GetDefaultMargin()); + int titleVMargin = DPExtensions.ConvertToScaledPixel(this.GetDefaultTitleVMargin()); + + var bound = Geometry; + + var menuBound = bound; + menuBound.X += menuMargin; + menuBound.Y += (menuBound.Height - menuSize) / 2; + menuBound.Width = menuSize; + menuBound.Height = menuSize; + + _menuButton.Geometry = menuBound; + + var contentBound = Geometry; + contentBound.X = menuBound.Right + titleHMargin; + contentBound.Y += titleVMargin; + contentBound.Width -= (menuBound.Width + menuMargin + titleHMargin * 2); + contentBound.Height -= titleVMargin * 2; + + if (_titleView != null) + { + _nativeTitleView.Geometry = contentBound; + } + else + { + _title.Geometry = contentBound; + } + } + + void IFlyoutBehaviorObserver.OnFlyoutBehaviorChanged(FlyoutBehavior behavior) + { + FlyoutBehavior = behavior; + } + } +} diff --git a/src/Controls/src/Core/Platform/Tizen/Shell/ShellSectionStack.cs b/src/Controls/src/Core/Platform/Tizen/Shell/ShellSectionStack.cs new file mode 100644 index 000000000000..ca5a393498c6 --- /dev/null +++ b/src/Controls/src/Core/Platform/Tizen/Shell/ShellSectionStack.cs @@ -0,0 +1,283 @@ +using System; +using System.ComponentModel; +using System.Threading.Tasks; +using ElmSharp; +using EBox = ElmSharp.Box; + +namespace Microsoft.Maui.Controls.Platform +{ + public class ShellSectionStack : EBox, IAppearanceObserver, IDisposable + { + ShellNavBar _navBar = null; + Page _currentPage = null; + SimpleViewStack _viewStack = null; + IShellSectionRenderer _shellSectionView; + + bool _disposed = false; + bool _navBarIsVisible = true; + + public ShellSectionStack(ShellSection section, IMauiContext context) : base(context?.Context?.BaseLayout) + { + ShellSection = section; + MauiContext = context; + InitializeComponent(); + } + + protected IMauiContext MauiContext { get; private set; } + + protected EvasObject NativeParent + { + get => MauiContext?.Context?.BaseLayout; + } + + public virtual bool NavBarIsVisible + { + get + { + return _navBarIsVisible; + } + set + { + _navBarIsVisible = value; + OnLayout(); + } + } + + ShellSection ShellSection { get; } + + ~ShellSectionStack() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + (Shell.Current as IShellController).RemoveAppearanceObserver(this); + (Shell.Current as IShellController).RemoveFlyoutBehaviorObserver(_navBar); + if (ShellSection != null) + { + IShellSectionController controller = ShellSection; + controller.NavigationRequested -= OnNavigationRequested; + controller.RemoveDisplayedPageObserver(this); + } + if (_currentPage != null) + { + _currentPage.PropertyChanged -= OnPagePropertyChanged; + } + if (_navBar != null) + { + _navBar.Dispose(); + _navBar = null; + } + Unrealize(); + } + _disposed = true; + } + + protected virtual IShellSectionRenderer CreateShellSectionView(ShellSection section) + { + return new ShellSectionView(section, MauiContext); + } + + void InitializeComponent() + { + SetAlignment(-1, -1); + SetWeight(1, 1); + SetLayoutCallback(OnLayout); + + _viewStack = new SimpleViewStack(NativeParent); + if (Device.Idiom == TargetIdiom.Phone) + { + _viewStack.BackgroundColor = ElmSharp.Color.White; + } + _viewStack.Show(); + PackEnd(_viewStack); + + _navBar = new ShellNavBar(MauiContext); + _navBar.Show(); + PackEnd(_navBar); + + IShellSectionController controller = ShellSection; + controller.NavigationRequested += OnNavigationRequested; + controller.AddDisplayedPageObserver(this, UpdateDisplayedPage); + ((IShellController)Shell.Current).AddAppearanceObserver(this, ShellSection); + ((IShellController)Shell.Current).AddFlyoutBehaviorObserver(_navBar); + + _shellSectionView = CreateShellSectionView(ShellSection); + _shellSectionView.NativeView.Show(); + _viewStack.Push(_shellSectionView.NativeView); + + Device.BeginInvokeOnMainThread(() => + { + (_shellSectionView.NativeView as Widget)?.SetFocus(true); + }); + } + + void UpdateDisplayedPage(Page page) + { + // this callback is raised when DisplayPage was updated and it is raised ahead of push NavigationRequesed event + if (_currentPage != null) + { + _currentPage.PropertyChanged -= OnPagePropertyChanged; + } + if (page == null) + return; + + _currentPage = page; + _currentPage.PropertyChanged += OnPagePropertyChanged; + NavBarIsVisible = Shell.GetNavBarIsVisible(page); + _navBar.SetPage(page); + } + + void IAppearanceObserver.OnAppearanceChanged(ShellAppearance appearance) + { + if (_navBar == null) + return; + + var titleColor = (appearance as IShellAppearanceElement)?.EffectiveTabBarTitleColor; + var backgroundColor = appearance?.BackgroundColor; + var foregroundColor = appearance?.ForegroundColor; + + _navBar.TitleColor = titleColor.IsDefault() ? ShellView.DefaultTitleColor : titleColor.ToNativeEFL(); + _navBar.BackgroundColor = backgroundColor.IsDefault() ? ShellView.DefaultBackgroundColor : backgroundColor.ToNativeEFL(); + _navBar.ForegroundColor = foregroundColor.IsDefault() ? ShellView.DefaultForegroundColor : foregroundColor.ToNativeEFL(); + } + + + protected virtual void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Page.TitleProperty.PropertyName) + { + _navBar.Title = (sender as Page)?.Title; + } + else if (e.PropertyName == Shell.NavBarIsVisibleProperty.PropertyName) + { + NavBarIsVisible = Shell.GetNavBarIsVisible(sender as Page); + } + else if (e.PropertyName == Shell.TitleViewProperty.PropertyName) + { + _navBar.TitleView = Shell.GetTitleView(sender as Page); + } + } + + void OnNavigationRequested(object sender, Internals.NavigationRequestedEventArgs e) + { + if (e.RequestType == Internals.NavigationRequestType.Push) + { + PushRequest(sender, e); + } + else if (e.RequestType == Internals.NavigationRequestType.Insert) + { + InsertRequest(sender, e); + } + else if (e.RequestType == Internals.NavigationRequestType.Pop) + { + PopRequest(sender, e); + } + else if (e.RequestType == Internals.NavigationRequestType.PopToRoot) + { + PopToRootRequest(sender, e); + } + else if (e.RequestType == Internals.NavigationRequestType.Remove) + { + RemoveRequest(sender, e); + } + UpdateHasBackButton(); + } + + void RemoveRequest(object sender, Internals.NavigationRequestedEventArgs request) + { + var nativePage = request.Page.ToNative(MauiContext); + if (nativePage == null) + { + request.Task = Task.FromException(new ArgumentException("Can't found page on stack", nameof(request.Page))); + return; + } + _viewStack.Remove(nativePage); + request.Task = Task.FromResult(true); + } + + void PopRequest(object sender, Internals.NavigationRequestedEventArgs request) + { + _viewStack.Pop(); + request.Task = Task.FromResult(true); + } + + void PopToRootRequest(object sender, Internals.NavigationRequestedEventArgs request) + { + _viewStack.PopToRoot(); + request.Task = Task.FromResult(true); + } + + void PushRequest(object sender, Internals.NavigationRequestedEventArgs request) + { + var nativePage = request.Page.ToNative(MauiContext); + _viewStack.Push(nativePage); + request.Task = Task.FromResult(true); + Device.BeginInvokeOnMainThread(() => + { + (nativePage as Widget)?.SetFocus(true); + }); + } + + void InsertRequest(object sender, Internals.NavigationRequestedEventArgs request) + { + var before = request.BeforePage.ToNative(MauiContext); + if (before == null) + { + request.Task = Task.FromException(new ArgumentException("Can't found page on stack", nameof(request.BeforePage))); + return; + } + var page = request.Page.ToNative(MauiContext); + _viewStack.Insert(before, page); + request.Task = Task.FromResult(true); + } + + void UpdateHasBackButton() + { + if (_viewStack.Stack.Count > 1) + _navBar.HasBackButton = true; + else + _navBar.HasBackButton = false; + } + + void OnLayout() + { + if (Geometry.Width == 0 || Geometry.Height == 0) + return; + + var bound = Geometry; + int navBarHeight; + if (NavBarIsVisible) + { + var navBound = bound; + navBarHeight = DPExtensions.ConvertToScaledPixel(_navBar.GetDefaultHeight()); + navBound.Height = navBarHeight; + + _navBar.Show(); + _navBar.Geometry = navBound; + _navBar.RaiseTop(); + } + else + { + navBarHeight = 0; + _navBar.Hide(); + } + + bound.Y += navBarHeight; + bound.Height -= navBarHeight; + _viewStack.Geometry = bound; + } + } +} diff --git a/src/Controls/src/Core/Platform/Tizen/Shell/ShellSectionView.cs b/src/Controls/src/Core/Platform/Tizen/Shell/ShellSectionView.cs new file mode 100644 index 000000000000..8ecd27799ed1 --- /dev/null +++ b/src/Controls/src/Core/Platform/Tizen/Shell/ShellSectionView.cs @@ -0,0 +1,376 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using ElmSharp; +using Tizen.UIExtensions.ElmSharp; +using Tizen.UIExtensions.Shell; +using EBox = ElmSharp.Box; +using EColor = ElmSharp.Color; +using EToolbarItem = ElmSharp.ToolbarItem; +using EToolbarItemEventArgs = ElmSharp.ToolbarItemEventArgs; + +namespace Microsoft.Maui.Controls.Platform +{ + public interface IShellSectionRenderer : IDisposable + { + EvasObject NativeView { get; } + } + + public class ShellSectionView : IAppearanceObserver, IShellSectionRenderer + { + EBox _mainLayout = null; + EBox _contentArea = null; + Tabs _tabs = null; + EvasObject _currentContent = null; + Page _displayedPage; + + Dictionary _contentCache = new Dictionary(); + Dictionary _contentToTabsItem = new Dictionary(); + Dictionary _itemToContent = new Dictionary(); + List _tabsItems = new List(); + + EColor _backgroundColor = ShellView.DefaultBackgroundColor; + EColor _foregroundColor = ShellView.DefaultForegroundColor; + + bool _disposed = false; + + public ShellSectionView(ShellSection section, IMauiContext context) + { + ShellSection = section; + MauiContext = context; + ShellSection.PropertyChanged += OnSectionPropertyChanged; + (ShellSection.Items as INotifyCollectionChanged).CollectionChanged += OnShellSectionCollectionChanged; + + _mainLayout = new EBox(NativeParent); + _mainLayout.SetLayoutCallback(OnLayout); + + _contentArea = new EBox(NativeParent); + _contentArea.Show(); + _mainLayout.PackEnd(_contentArea); + + UpdateTabsItem(); + UpdateCurrentItem(ShellSection.CurrentItem); + + ((IShellController)Shell.Current).AddAppearanceObserver(this, ShellSection); + (ShellSection as IShellSectionController).AddDisplayedPageObserver(this, UpdateDisplayedPage); + } + + bool HasTabs => _tabs != null; + + bool _tabBarIsVisible = true; + + protected IMauiContext MauiContext { get; private set; } + + protected EvasObject NativeParent + { + get => MauiContext?.Context?.BaseLayout; + } + + protected virtual bool TabBarIsVisible + { + get => _tabBarIsVisible; + set + { + if (_tabBarIsVisible != value) + { + _tabBarIsVisible = value; + _mainLayout.MarkChanged(); + + if (value) + { + _tabs?.Show(); + } + else + { + _tabs?.Hide(); + } + } + } + } + + public ShellSection ShellSection { get; } + + public EvasObject NativeView + { + get + { + return _mainLayout; + } + } + + public EColor ToolbarBackgroundColor + { + get + { + return _backgroundColor; + } + set + { + _backgroundColor = value; + UpdateToolbarBackgroudColor(_backgroundColor); + } + } + + public EColor ToolbarForegroundColor + { + get + { + return _foregroundColor; + } + set + { + _foregroundColor = value; + UpdateToolbarForegroundColor(_foregroundColor); + } + } + + ~ShellSectionView() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + void IAppearanceObserver.OnAppearanceChanged(ShellAppearance appearance) + { + var backgroundColor = (appearance as IShellAppearanceElement)?.EffectiveTabBarBackgroundColor; + var foregroundColor = appearance?.ForegroundColor; + ToolbarBackgroundColor = backgroundColor.IsDefault() ? ShellView.DefaultBackgroundColor : backgroundColor.ToNativeEFL(); + ToolbarForegroundColor = foregroundColor.IsDefault() ? ShellView.DefaultForegroundColor : foregroundColor.ToNativeEFL(); + } + + void UpdateDisplayedPage(Page page) + { + if (_displayedPage != null) + { + _displayedPage.PropertyChanged -= OnDisplayedPagePropertyChanged; + } + + if (page == null) + { + TabBarIsVisible = true; + return; + } + _displayedPage = page; + _displayedPage.PropertyChanged += OnDisplayedPagePropertyChanged; + TabBarIsVisible = Shell.GetTabBarIsVisible(page); + } + + void OnDisplayedPagePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Shell.TabBarIsVisibleProperty.PropertyName) + { + TabBarIsVisible = Shell.GetTabBarIsVisible(_displayedPage); + } + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + ((IShellController)Shell.Current).RemoveAppearanceObserver(this); + if (ShellSection != null) + { + (ShellSection as IShellSectionController).RemoveDisplayedPageObserver(this); + ShellSection.PropertyChanged -= OnSectionPropertyChanged; + DeinitializeTabs(); + + foreach (var native in _contentCache.Values) + { + native.Unrealize(); + } + _contentCache.Clear(); + _contentToTabsItem.Clear(); + _itemToContent.Clear(); + } + NativeView.Unrealize(); + } + _disposed = true; + } + + void InitializeTabs() + { + if (_tabs != null) + { + return; + } + _tabs = new Tabs(NativeParent); + _tabs.Show(); + _tabs.BackgroundColor = _backgroundColor; + _tabs.Scrollable = TabsType.Fixed; + _tabs.Selected += OnTabsSelected; + _mainLayout.PackEnd(_tabs); + } + + void ClearTabsItem() + { + if (!HasTabs) + return; + + foreach (var item in _tabsItems) + { + item.Delete(); + } + _tabsItems.Clear(); + _contentToTabsItem.Clear(); + _itemToContent.Clear(); + } + + void DeinitializeTabs() + { + if (_tabs == null) + { + return; + } + ClearTabsItem(); + _tabs.Unrealize(); + _tabs = null; + } + + void OnSectionPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "CurrentItem") + { + UpdateCurrentItem(ShellSection.CurrentItem); + } + } + + void UpdateCurrentItem(ShellContent content) + { + UpdateCurrentShellContent(content); + if (_contentToTabsItem.ContainsKey(content)) + { + _contentToTabsItem[content].IsSelected = true; + } + } + + void UpdateToolbarBackgroudColor(EColor color) + { + foreach (EToolbarItem item in _tabsItems) + { + item.SetBackgroundColor(color); + } + } + + void UpdateToolbarForegroundColor(EColor color) + { + foreach (EToolbarItem item in _tabsItems) + { + item.SetUnderlineColor(color); + } + } + + void UpdateTabsItem() + { + if (ShellSection.Items.Count <= 1) + { + DeinitializeTabs(); + return; + } + + InitializeTabs(); + ClearTabsItem(); + foreach (ShellContent content in ShellSection.Items) + { + InsertTabsItem(content); + } + _tabs.Scrollable = ShellSection.Items.Count > 3 ? TabsType.Scrollable : TabsType.Fixed; + } + + EToolbarItem InsertTabsItem(ShellContent content) + { + EToolbarItem item = _tabs.Append(content.Title, null); + item.SetBackgroundColor(_backgroundColor); + item.SetUnderlineColor(_foregroundColor); + + _tabsItems.Add(item); + _itemToContent[item] = content; + _contentToTabsItem[content] = item; + return item; + } + + void OnShellSectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + UpdateTabsItem(); + } + + void OnTabsSelected(object sender, EToolbarItemEventArgs e) + { + if (_tabs.SelectedItem == null) + { + return; + } + + ShellContent content = _itemToContent[_tabs.SelectedItem]; + if (ShellSection.CurrentItem != content) + { + ShellSection.SetValueFromRenderer(ShellSection.CurrentItemProperty, content); + } + } + + void UpdateCurrentShellContent(ShellContent content) + { + if (_currentContent != null) + { + _currentContent.Hide(); + _contentArea.UnPack(_currentContent); + _currentContent = null; + } + + if (content == null) + { + return; + } + + if (!_contentCache.ContainsKey(content)) + { + var native = CreateShellContent(content); + native.SetAlignment(-1, -1); + native.SetWeight(1, 1); + _contentCache[content] = native; + } + _currentContent = _contentCache[content]; + _currentContent.Show(); + _contentArea.PackEnd(_currentContent); + } + + EvasObject CreateShellContent(ShellContent content) + { + Page xpage = ((IShellContentController)content).GetOrCreateContent(); + return xpage.ToNative(MauiContext); + } + + void OnLayout() + { + if (NativeView.Geometry.Width == 0 || NativeView.Geometry.Height == 0) + return; + var bound = NativeView.Geometry; + + int tabsHeight; + if (HasTabs && TabBarIsVisible) + { + var tabsBound = bound; + tabsHeight = _tabs.MinimumHeight; + tabsBound.Height = tabsHeight; + _tabs.Geometry = tabsBound; + } + else + { + tabsHeight = 0; + } + + var contentBound = bound; + contentBound.Y += tabsHeight; + contentBound.Height -= tabsHeight; + _contentArea.Geometry = contentBound; + } + } +} diff --git a/src/Controls/src/Core/Platform/Tizen/Shell/ShellView.cs b/src/Controls/src/Core/Platform/Tizen/Shell/ShellView.cs new file mode 100644 index 000000000000..eab36c79a4c8 --- /dev/null +++ b/src/Controls/src/Core/Platform/Tizen/Shell/ShellView.cs @@ -0,0 +1,339 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using ElmSharp; +using Tizen.UIExtensions.Common; +using Tizen.UIExtensions.ElmSharp; +using EColor = ElmSharp.Color; +using TINavigationView = Tizen.UIExtensions.Shell.INavigationView; +using TNavigationView = Tizen.UIExtensions.Shell.NavigationView; +using TThemeConstants = Tizen.UIExtensions.Shell.ThemeConstants; +using TCollectionView = Tizen.UIExtensions.ElmSharp.CollectionView; +using TSelectedItemChangedEventArgs = Tizen.UIExtensions.ElmSharp.SelectedItemChangedEventArgs; + +namespace Microsoft.Maui.Controls.Platform +{ + public class ShellView : NavigationDrawer, IFlyoutBehaviorObserver + { + TINavigationView _navigationView; + FlyoutHeaderBehavior _headerBehavior; + + List> _cachedGroups; + + View _headerView; + View _footerView; + TCollectionView _itemsView; + + Element _lastSelected; + ShellItemView _currentShellItem; + + public static readonly EColor DefaultBackgroundColor = TThemeConstants.Shell.ColorClass.DefaultBackgroundColor; + public static readonly EColor DefaultForegroundColor = TThemeConstants.Shell.ColorClass.DefaultForegroundColor; + public static readonly EColor DefaultTitleColor = TThemeConstants.Shell.ColorClass.DefaultTitleColor; + + // The source of icon resources is https://materialdesignicons.com/ + public const string MenuIcon = ""; + + protected EvasObject NativeParent { get; private set; } + + protected Shell Element { get; private set; } + + public IMauiContext MauiContext { get; private set; } + + public NavigationDrawer NativeView => this; + + bool HeaderOnMenu => _headerBehavior == FlyoutHeaderBehavior.Scroll || + _headerBehavior == FlyoutHeaderBehavior.CollapseOnScroll; + + public ShellView(EvasObject parent) : base(parent) + { + NativeParent = parent; + _navigationView = CreateNavigationView(); + _navigationView.LayoutUpdated += OnNavigationViewLayoutUpdated; + + NavigationView = _navigationView.TargetView; + Toggled += OnDrawerToggled; + + _navigationView.Content = CreateItemsView(); + } + + internal void SetElement(Shell shell, IMauiContext context) + { + Element = shell; + Element.PropertyChanged += OnElementPropertyChanged; + MauiContext = context; + + ((IShellController)Element).StructureChanged += OnShellStructureChanged; + _lastSelected = null; + + UpdateFlyoutIsPresented(); + UpdateCurrentItem(); + UpdateFlyoutHeader(); + UpdateFooter(); + } + + protected virtual ShellItemView CreateShellItemView(ShellItem item) + { + return new ShellItemView(item, MauiContext); + } + + protected virtual TINavigationView CreateNavigationView() + { + return new TNavigationView(NativeParent); + } + + protected virtual EvasObject CreateItemsView() + { + _itemsView = new TCollectionView(NativeParent) + { + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1, + WeightY = 1, + SelectionMode = CollectionViewSelectionMode.Single, + HorizontalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Invisible, + VerticalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Invisible, + LayoutManager = new LinearLayoutManager(false, Tizen.UIExtensions.ElmSharp.ItemSizingStrategy.MeasureFirstItem) + }; + + return _itemsView; + } + + protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Shell.CurrentItemProperty.PropertyName) + { + UpdateCurrentItem(); + } + else if (e.PropertyName == Shell.FlyoutIsPresentedProperty.PropertyName) + { + UpdateFlyoutIsPresented(); + } + else if (e.PropertyName == Shell.FlyoutBackgroundColorProperty.PropertyName) + { + UpdateFlyoutBackgroundColor(); + } + else if (e.PropertyName == Shell.FlyoutHeaderProperty.PropertyName) + { + UpdateFlyoutHeader(); + } + else if (e.PropertyName == Shell.FlyoutHeaderTemplateProperty.PropertyName) + { + UpdateFlyoutHeader(); + } + else if (e.PropertyName == Shell.FlyoutHeaderBehaviorProperty.PropertyName) + { + UpdateFlyoutHeader(); + } + else if (e.PropertyName == Shell.FlyoutFooterProperty.PropertyName) + { + UpdateFooter(); + } + } + + protected virtual void UpdateFlyoutIsPresented() + { + // It is workaround of Panel.IsOpen bug, Panel.IsOpen property is not working when layouting was triggered + Device.BeginInvokeOnMainThread(() => + { + IsOpen = Element.FlyoutIsPresented; + }); + } + + protected void OnDrawerToggled(object sender, EventArgs e) + { + Element.SetValueFromRenderer(Shell.FlyoutIsPresentedProperty, IsOpen); + } + + protected virtual void UpdateFlyoutBehavior() + { + if (Element.FlyoutBehavior == FlyoutBehavior.Locked) + { + IsSplit = true; + } + else + { + IsSplit = false; + } + } + + protected virtual void BuildMenu() + { + var groups = ((IShellController)Element).GenerateFlyoutGrouping(); + + if (!IsItemChanged(groups) && !HeaderOnMenu) + return; + + _cachedGroups = groups; + + var items = new List(); + + foreach (var group in groups) + { + bool isFirst = true; + foreach (var item in group) + { + items.Add(item); + + // TODO: implements separator + if (isFirst) + isFirst = false; + } + } + + _itemsView.Adaptor = new ShellFlyoutItemAdaptor(Element, MauiContext, items, HeaderOnMenu); + _itemsView.Adaptor.ItemSelected += OnItemSelected; + } + + protected virtual void UpdateFlyoutHeader() + { + if (_headerView != null) + { + _headerView.MeasureInvalidated -= OnHeaderSizeChanged; + _headerView = null; + } + + _headerView = (Element as IShellController).FlyoutHeader; + _headerBehavior = Element.FlyoutHeaderBehavior; + + BuildMenu(); + + if (_headerView != null) + { + if (HeaderOnMenu) + { + _navigationView.Header = null; + } + else + { + _navigationView.Header = _headerView?.ToNative(MauiContext); + _headerView.MeasureInvalidated += OnHeaderSizeChanged; + } + } + else + { + _navigationView.Header = null; + } + } + + protected virtual void UpdateFooter() + { + if (_footerView != null) + { + _footerView.MeasureInvalidated -= OnFooterSizeChanged; + _footerView = null; + } + + _footerView = (Element as IShellController).FlyoutFooter; + + if (_footerView != null) + { + _navigationView.Footer = _footerView?.ToNative(MauiContext); + _footerView.MeasureInvalidated += OnFooterSizeChanged; + } + else + { + _navigationView.Footer = null; + } + } + + void OnShellStructureChanged(object sender, EventArgs e) + { + BuildMenu(); + } + + void OnItemSelected(object sender, TSelectedItemChangedEventArgs e) + { + _lastSelected = e.SelectedItem as Element; + ((IShellController)Element).OnFlyoutItemSelected(_lastSelected); + } + + bool IsItemChanged(List> groups) + { + if (_cachedGroups == null) + return true; + + if (_cachedGroups.Count != groups.Count) + return true; + + for (int i = 0; i < groups.Count; i++) + { + if (_cachedGroups[i].Count != groups[i].Count) + return true; + + for (int j = 0; j < groups[i].Count; j++) + { + if (_cachedGroups[i][j] != groups[i][j]) + return true; + } + } + + _cachedGroups = groups; + return false; + } + + void UpdateCurrentItem() + { + _currentShellItem?.Dispose(); + if (Element.CurrentItem != null) + { + _currentShellItem = CreateShellItemView(Element.CurrentItem); + Main = _currentShellItem.NativeView; + } + else + { + Main = null; + } + } + + void UpdateFlyoutBackgroundColor() + { + _navigationView.BackgroundColor = Element.FlyoutBackgroundColor.ToNativeEFL(); + } + + void OnNavigationViewLayoutUpdated(object sender, LayoutEventArgs args) + { + UpdateHeaderLayout(args.Geometry.Width, args.Geometry.Height); + UpdateFooterLayout(args.Geometry.Width, args.Geometry.Height); + } + + void OnHeaderSizeChanged(object sender, EventArgs e) + { + var bound = (_navigationView as EvasObject).Geometry; + Device.BeginInvokeOnMainThread(()=> { + UpdateHeaderLayout(bound.Width, bound.Height); + }); + } + + void OnFooterSizeChanged(object sender, EventArgs e) + { + var bound = (_navigationView as EvasObject).Geometry; + Device.BeginInvokeOnMainThread(() => { + UpdateFooterLayout(bound.Width, bound.Height); + }); + } + + void UpdateHeaderLayout(double widthConstraint, double heightConstraint) + { + if ((!HeaderOnMenu) && (_headerView != null)) + { + var requestSize = _headerView.Measure(widthConstraint, heightConstraint); + _navigationView.Header.MinimumHeight = DPExtensions.ConvertToScaledPixel(requestSize.Request.Height); + } + } + + void UpdateFooterLayout(double widthConstraint, double heightConstraint) + { + if (_footerView != null) + { + var requestSize = _footerView.Measure(widthConstraint, heightConstraint); + _navigationView.Footer.MinimumHeight = DPExtensions.ConvertToScaledPixel(requestSize.Request.Height); + } + } + + void IFlyoutBehaviorObserver.OnFlyoutBehaviorChanged(FlyoutBehavior behavior) + { + UpdateFlyoutBehavior(); + } + } +} diff --git a/src/Controls/src/Core/Platform/Tizen/Shell/SimpleViewStack.cs b/src/Controls/src/Core/Platform/Tizen/Shell/SimpleViewStack.cs new file mode 100644 index 000000000000..0def6830d4d2 --- /dev/null +++ b/src/Controls/src/Core/Platform/Tizen/Shell/SimpleViewStack.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using System.Linq; +using ElmSharp; +using EBox = ElmSharp.Box; + +namespace Microsoft.Maui.Controls.Platform +{ + public class SimpleViewStack : EBox + { + EvasObject _lastTop; + + public SimpleViewStack(EvasObject parent) : base(parent) + { + InternalStack = new List(); + SetLayoutCallback(OnLayout); + } + + List InternalStack { get; set; } + + public IReadOnlyList Stack => InternalStack; + + public void Push(EvasObject view) + { + InternalStack.Add(view); + PackEnd(view); + UpdateTopView(); + } + + public void Pop() + { + if (_lastTop != null) + { + var tobeRemoved = _lastTop; + InternalStack.Remove(tobeRemoved); + UnPack(tobeRemoved); + UpdateTopView(); + // if Pop was called by removed page, + // Unrealize cause deletation of NativeCallback, it could be a cause of crash + Device.BeginInvokeOnMainThread(() => + { + tobeRemoved.Unrealize(); + }); + } + } + + public void PopToRoot() + { + while (InternalStack.Count > 1) + { + Pop(); + } + } + + public void Insert(EvasObject before, EvasObject view) + { + view.Hide(); + var idx = InternalStack.IndexOf(before); + InternalStack.Insert(idx, view); + PackEnd(view); + UpdateTopView(); + } + + public void Remove(EvasObject view) + { + InternalStack.Remove(view); + UnPack(view); + UpdateTopView(); + Device.BeginInvokeOnMainThread(() => + { + view?.Unrealize(); + }); + } + + void UpdateTopView() + { + if (_lastTop != InternalStack.LastOrDefault()) + { + _lastTop?.Hide(); + _lastTop = InternalStack.LastOrDefault(); + _lastTop.Show(); + (_lastTop as Widget)?.SetFocus(true); + } + } + + void OnLayout() + { + foreach (var view in Stack) + { + view.Geometry = Geometry; + } + } + } +} diff --git a/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/INavigationView.cs b/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/INavigationView.cs new file mode 100644 index 000000000000..8a89f957ee94 --- /dev/null +++ b/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/INavigationView.cs @@ -0,0 +1,24 @@ +using System; +using ElmSharp; +using Tizen.UIExtensions.Common; +using EColor = ElmSharp.Color; + +namespace Tizen.UIExtensions.Shell +{ + public interface INavigationView + { + EvasObject TargetView { get; } + + EvasObject Header { get; set; } + + EvasObject Footer { get; set; } + + EvasObject Content { get; set; } + + EColor BackgroundColor { get; set; } + + EvasObject BackgroundImage { get; set; } + + event EventHandler LayoutUpdated; + } +} diff --git a/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/ITabs.cs b/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/ITabs.cs new file mode 100644 index 000000000000..2f7c7c1c6223 --- /dev/null +++ b/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/ITabs.cs @@ -0,0 +1,30 @@ +using System; +using ElmSharp; +using EColor = ElmSharp.Color; +using EToolbarItemEventArgs = ElmSharp.ToolbarItemEventArgs; + +namespace Tizen.UIExtensions.Shell +{ + public interface ITabs + { + TabsType Scrollable { get; set; } + + EColor BackgroundColor { get; set; } + + ToolbarItem SelectedItem { get; } + + event EventHandler Selected; + + ToolbarItem Append(string label, string icon); + + ToolbarItem Append(string label); + + ToolbarItem InsertBefore(ToolbarItem before, string label, string icon); + } + + public enum TabsType + { + Fixed, + Scrollable + } +} diff --git a/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/NavigationView.cs b/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/NavigationView.cs new file mode 100644 index 000000000000..72fcb594a9ca --- /dev/null +++ b/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/NavigationView.cs @@ -0,0 +1,205 @@ +using ElmSharp; +using System; +using Tizen.UIExtensions.Common; +using Tizen.UIExtensions.ElmSharp; +using EColor = ElmSharp.Color; +using EBox = ElmSharp.Box; + +namespace Tizen.UIExtensions.Shell +{ + /// + /// The native widget that is configured with an header and an list of items to be used in NavigationDrawer. + /// + public class NavigationView : Background, INavigationView + { + static readonly EColor s_defaultBackgroundColor = ElmSharp.ThemeConstants.Shell.ColorClass.DefaultNavigationViewBackgroundColor; + + EBox _mainLayout; + + EvasObject _header; + EvasObject _footer; + EvasObject _content; + + EvasObject _backgroundImage; + EColor _backgroundColor; + + /// + /// Initializes a new instance of the class. + /// + /// Parent evas object. + public NavigationView(EvasObject parent) : base(parent) + { + InitializeComponent(parent); + } + + /// + /// Gets or sets the background color of the NavigtiaonView. + /// + public override EColor BackgroundColor + { + get => _backgroundColor; + set + { + _backgroundColor = value; + EColor effectiveColor = _backgroundColor.IsDefault ? s_defaultBackgroundColor : _backgroundColor; + base.BackgroundColor = effectiveColor; + } + } + + /// + /// Gets or sets the background image of the NavigtiaonView. + /// + public EvasObject BackgroundImage + { + get => _backgroundImage; + set + { + _backgroundImage = value; + this.SetBackgroundPart(_backgroundImage); + } + } + + /// + /// Gets or sets the header view of the NavigtiaonView. + /// + public EvasObject Header + { + get => _header; + set => UpdateHeader(value); + } + + /// + /// Gets or sets the footer view of the NavigtiaonView. + /// + public EvasObject Footer + { + get => _footer; + set => UpdateFooter(value); + } + + public EvasObject Content + { + get => _content; + set => UpdateContent(value); + } + + /// + /// Gets or sets the target view of the NavigtiaonView. + /// + public EvasObject TargetView => this; + + /// + /// Notifies that the layout has been updated. + /// + public event EventHandler LayoutUpdated; + + void InitializeComponent(EvasObject parent) + { + base.BackgroundColor = s_defaultBackgroundColor; + + _mainLayout = new EBox(parent) + { + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1, + WeightY = 1 + }; + _mainLayout.SetLayoutCallback(OnLayout); + _mainLayout.Show(); + + SetContent(_mainLayout); + } + + void OnLayout() + { + if (Geometry.Width == 0 || Geometry.Height == 0) + return; + + var bound = Geometry; + int headerHeight = 0; + int footerHeight = 0; + + if (_header != null) + { + var headerBound = bound; + headerHeight = _header.MinimumHeight; + headerBound.Height = headerHeight; + _header.Geometry = headerBound; + } + + if (_footer != null) + { + var footerbound = bound; + footerHeight = _footer.MinimumHeight; + footerbound.Y = bound.Y + bound.Height - footerHeight; + footerbound.Height = footerHeight; + _footer.Geometry = footerbound; + } + + if (_content != null) + { + bound.Y += headerHeight; + bound.Height = bound.Height - headerHeight - footerHeight; + _content.Geometry = bound; + } + + NotifyOnLayout(); + } + + void NotifyOnLayout() + { + LayoutUpdated?.Invoke(this, new LayoutEventArgs() { Geometry = Geometry.ToCommon() }); + } + + void UpdateHeader(EvasObject header) + { + if (_header != null) + { + _mainLayout.UnPack(_header); + _header.Unrealize(); + _header = null; + } + + if (header != null) + { + _mainLayout.PackStart(header); + } + _header = header; + _header?.Show(); + } + + void UpdateFooter(EvasObject footer) + { + if (_footer != null) + { + _mainLayout.UnPack(_footer); + _footer.Unrealize(); + _footer = null; + } + + if (footer != null) + { + _mainLayout.PackEnd(footer); + } + _footer = footer; + _footer?.Show(); + } + + void UpdateContent(EvasObject content) + { + if (_content != null) + { + _mainLayout.UnPack(_content); + _content.Unrealize(); + _content = null; + } + + if (content != null) + { + _mainLayout.PackEnd(content); + } + _content = content; + _content?.Show(); + } + } +} diff --git a/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/Tabs.cs b/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/Tabs.cs new file mode 100644 index 000000000000..0046969b13cb --- /dev/null +++ b/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/Tabs.cs @@ -0,0 +1,35 @@ +using ElmSharp; +using Tizen.UIExtensions.ElmSharp; +using EToolbar = ElmSharp.Toolbar; + +namespace Tizen.UIExtensions.Shell +{ + public class Tabs : EToolbar, ITabs + { + TabsType _type; + + public Tabs(EvasObject parent) : base(parent) + { + Style = ElmSharp.ThemeConstants.Toolbar.Styles.Material; + SelectionMode = ToolbarSelectionMode.Always; + } + + public TabsType Scrollable + { + get => _type; + set + { + switch (value) + { + case TabsType.Fixed: + this.ShrinkMode = ToolbarShrinkMode.Expand; + break; + case TabsType.Scrollable: + this.ShrinkMode = ToolbarShrinkMode.Scroll; + break; + } + _type = value; + } + } + } +} diff --git a/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/ThemeConstants.cs b/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/ThemeConstants.cs new file mode 100644 index 000000000000..b91c87d53211 --- /dev/null +++ b/src/Controls/src/Core/Platform/Tizen/Shell/Tizen.UIExtensions/ThemeConstants.cs @@ -0,0 +1,25 @@ +using EColor = ElmSharp.Color; + +namespace Tizen.UIExtensions.Shell +{ + public class ThemeConstants + { + public class Shell + { + public class ColorClass + { + public static readonly EColor DefaultBackgroundColor = EColor.FromRgb(33, 150, 243); + public static readonly EColor DefaultForegroundColor = EColor.White; + public static readonly EColor DefaultTitleColor = EColor.White; + } + + public class Resources + { + // The source of icon resources is https://materialdesignicons.com/ + public const string MenuIcon = "Platform.Tizen.Resources.menu.png"; + public const string BackIcon = "Platform.Tizen.Resources.arrow_left.png"; + public const string DotsIcon = "Platform.Tizen.Resources.dots_horizontal.png"; + } + } + } +}