using System; using System.Collections.Generic; using System.Collections.Specialized; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Markup; using Microsoft.UI.Xaml.Media; namespace Sonex.Client.Controls; [ContentProperty(Name = nameof(Items))] public sealed class TabPanel : UserControl { public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register( nameof(SelectedItem), typeof(TabPanelItem), typeof(TabPanel), new PropertyMetadata(null, OnSelectedItemChanged)); public static readonly DependencyProperty TabHeightProperty = DependencyProperty.Register( nameof(TabHeight), typeof(double), typeof(TabPanel), new PropertyMetadata(double.NaN, OnTabAppearanceChanged)); public static readonly DependencyProperty HeaderCommandBarProperty = DependencyProperty.Register( nameof(HeaderCommandBar), typeof(CommandBar), typeof(TabPanel), new PropertyMetadata(null, OnHeaderCommandBarChanged)); private readonly TabPanelItemCollection _items = []; private readonly List _trackedItems = []; private readonly SelectorBar _selectorBar; private readonly ContentPresenter _selectedContentPresenter; private readonly ContentPresenter _headerCommandBarPresenter; private bool _isUpdatingSelection; public TabPanel() { Grid layoutRoot = new() { RowSpacing = 0 }; layoutRoot.CornerRadius = new CornerRadius(10); layoutRoot.Padding = new Thickness(0, 0, 0, 0); layoutRoot.BorderThickness = new Thickness(1); layoutRoot.Background = Application.Current.Resources["CardBackgroundFillColorDefaultBrush"] as Brush; layoutRoot.BorderBrush = Application.Current.Resources["CardStrokeColorDefaultBrush"] as Brush; layoutRoot.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); layoutRoot.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }); Grid headerGrid = new() { ColumnSpacing = 8, Padding = new Thickness(8, 0, 0, 0), Height = 48d, BorderThickness = new Thickness(0, 0, 0, 1), BorderBrush = Application.Current.Resources["CardStrokeColorDefaultBrush"] as Brush }; headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); _selectorBar = new SelectorBar(); _selectorBar.Resources["SelectorBarItemPadding"] = new Thickness(0, 0, 0, 0); _selectorBar.Resources["SelectorBarPadding"] = new Thickness(0, 0, 0, 0); _selectorBar.VerticalAlignment = VerticalAlignment.Center; _headerCommandBarPresenter = new ContentPresenter { HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Center }; Grid.SetColumn(_selectorBar, 0); Grid.SetColumn(_headerCommandBarPresenter, 1); headerGrid.Children.Add(_selectorBar); headerGrid.Children.Add(_headerCommandBarPresenter); _selectedContentPresenter = new ContentPresenter(); _selectedContentPresenter.Background = Application.Current.Resources["CardBackgroundFillColorDefaultBrush"] as Brush; Grid.SetRow(headerGrid, 0); Grid.SetRow(_selectedContentPresenter, 1); layoutRoot.Children.Add(headerGrid); layoutRoot.Children.Add(_selectedContentPresenter); base.Content = layoutRoot; _selectorBar.SelectionChanged += SelectorBar_SelectionChanged; _items.CollectionChanged += Items_CollectionChanged; UpdateHeaderCommandBar(); EnsureSelection(); UpdateSelectedContent(); } public TabPanelItemCollection Items => _items; public TabPanelItem? SelectedItem { get => (TabPanelItem?)GetValue(SelectedItemProperty); set => SetValue(SelectedItemProperty, value); } public double TabHeight { get => (double)GetValue(TabHeightProperty); set => SetValue(TabHeightProperty, value); } public CommandBar? HeaderCommandBar { get => (CommandBar?)GetValue(HeaderCommandBarProperty); set => SetValue(HeaderCommandBarProperty, value); } public int SelectedIndex { get => SelectedItem is null ? -1 : Items.IndexOf(SelectedItem); set => SelectedItem = value >= 0 && value < Items.Count ? Items[value] : null; } public event EventHandler? SelectionChanged; private static void OnSelectedItemChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) { if (dependencyObject is TabPanel tabPanel) { tabPanel.HandleSelectedItemChanged((TabPanelItem?)args.OldValue, (TabPanelItem?)args.NewValue); } } private static void OnTabAppearanceChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs _) { if (dependencyObject is TabPanel tabPanel) { tabPanel.ApplyTabAppearance(); } } private static void OnHeaderCommandBarChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs _) { if (dependencyObject is TabPanel tabPanel) { tabPanel.UpdateHeaderCommandBar(); } } private void HandleSelectedItemChanged(TabPanelItem? oldItem, TabPanelItem? newItem) { if (_isUpdatingSelection) { return; } if (newItem is not null && !Items.Contains(newItem)) { _isUpdatingSelection = true; SetValue(SelectedItemProperty, oldItem); _isUpdatingSelection = false; return; } UpdateSelectorBarSelection(); UpdateSelectedContent(); if (!ReferenceEquals(oldItem, newItem)) { RaiseItemSelected(newItem); SelectionChanged?.Invoke(this, EventArgs.Empty); } } private void Items_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs _) { RebuildTrackedItems(); SyncItems(); EnsureSelection(); UpdateSelectedContent(); } private void RebuildTrackedItems() { foreach (TabPanelItem item in _trackedItems) { item.ContentChanged -= Item_ContentChanged; } _trackedItems.Clear(); foreach (TabPanelItem item in Items) { item.ContentChanged += Item_ContentChanged; _trackedItems.Add(item); } } private void SyncItems() { _selectorBar.Items.Clear(); foreach (TabPanelItem item in Items) { ApplyTabAppearance(item); _selectorBar.Items.Add(item); } } private void ApplyTabAppearance() { foreach (TabPanelItem item in Items) { ApplyTabAppearance(item); } } private void ApplyTabAppearance(TabPanelItem item) { if (double.IsNaN(TabHeight) || TabHeight <= 0) { item.ClearValue(HeightProperty); return; } item.Height = TabHeight; } private void EnsureSelection() { if (SelectedItem is not null && Items.Contains(SelectedItem)) { UpdateSelectorBarSelection(); return; } _isUpdatingSelection = true; SetValue(SelectedItemProperty, Items.Count > 0 ? Items[0] : null); _isUpdatingSelection = false; UpdateSelectorBarSelection(); } private void UpdateSelectorBarSelection() { _isUpdatingSelection = true; _selectorBar.SelectedItem = SelectedItem; _isUpdatingSelection = false; } private void UpdateSelectedContent() { _selectedContentPresenter.Content = SelectedItem?.Content; } private void UpdateHeaderCommandBar() { if (HeaderCommandBar != null) { HeaderCommandBar.DefaultLabelPosition = CommandBarDefaultLabelPosition.Right; } _headerCommandBarPresenter.Content = HeaderCommandBar; _headerCommandBarPresenter.Visibility = HeaderCommandBar is null ? Visibility.Collapsed : Visibility.Visible; } private void Item_ContentChanged(object? sender, EventArgs e) { if (ReferenceEquals(sender, SelectedItem)) { UpdateSelectedContent(); } } private void SelectorBar_SelectionChanged(SelectorBar sender, SelectorBarSelectionChangedEventArgs args) { TabPanelItem? selectedItem = sender.SelectedItem as TabPanelItem; if (ReferenceEquals(SelectedItem, selectedItem)) { return; } _isUpdatingSelection = true; SetValue(SelectedItemProperty, selectedItem); _isUpdatingSelection = false; UpdateSelectedContent(); RaiseItemSelected(selectedItem); SelectionChanged?.Invoke(this, EventArgs.Empty); } private void RaiseItemSelected(TabPanelItem? item) { item?.RaiseOnSelect(); } }