diff --git a/src/Controls/src/Core/Platform/Tizen/Extensions/ShellExtensions.cs b/src/Controls/src/Core/Platform/Tizen/Extensions/ShellExtensions.cs index fc30d0f8f202..53b70de2c3d3 100644 --- a/src/Controls/src/Core/Platform/Tizen/Extensions/ShellExtensions.cs +++ b/src/Controls/src/Core/Platform/Tizen/Extensions/ShellExtensions.cs @@ -14,4 +14,4 @@ public static DrawerBehavior ToPlatform(this FlyoutBehavior behavior) return DrawerBehavior.Drawer; } } -} \ No newline at end of file +} 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 000000000000..923dfeb34fef Binary files /dev/null and b/src/Controls/src/Core/Platform/Tizen/Resources/arrow_left.png differ diff --git a/src/Controls/src/Core/Platform/Tizen/Resources/dots_horizontal.png b/src/Controls/src/Core/Platform/Tizen/Resources/dots_horizontal.png new file mode 100644 index 000000000000..63d0f4be8891 Binary files /dev/null and b/src/Controls/src/Core/Platform/Tizen/Resources/dots_horizontal.png differ 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 000000000000..6dcfc0575a1d Binary files /dev/null and b/src/Controls/src/Core/Platform/Tizen/Resources/menu.png differ 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/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"; + } + } + } +}