using Lattice.Core.Docking.Abstractions;
using Lattice.Core.Docking.Engine;
using Lattice.Core.Docking.Models;
using Lattice.UI.Docking.Abstractions;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Lattice.UI;
///
/// Представляет кастомный контрол вкладок с поддержкой всех позиций размещения панели вкладок.
/// Реализует интерфейс для интеграции с системой докинга.
///
///
///
/// Контрол обеспечивает отображение коллекции вкладок с возможностью навигации между ними,
/// закрытия вкладок и изменения порядка. Поддерживает все четыре позиции размещения панели
/// вкладок: сверху, снизу, слева и справа.
///
///
/// Контрол автоматически синхронизирует свое состояние с моделью данных
/// и обеспечивает двустороннюю привязку данных через механизм INotifyPropertyChanged.
///
///
public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
{
private readonly PropertyChangedEventHandler _modelPropertyChangedHandler;
private bool _disposed;
private DockLeaf? _model;
private Grid? _rootGrid;
private ListBox? _tabHeaderList;
private ContentControl? _contentControl;
private LayoutManager? _layoutManager;
private IDockContextManager? _contextManager;
private bool _isSelected;
private bool _isActive;
private TabPlacement _tabPlacement = TabPlacement.Top;
private bool _showCloseButtons = true;
private bool _canReorderTabs = true;
///
/// Инициализирует новый экземпляр класса .
///
public LatticeTabControl()
{
this.DefaultStyleKey = typeof(LatticeTabControl);
_modelPropertyChangedHandler = OnModelPropertyChanged;
this.DataContextChanged += OnDataContextChanged;
}
///
public IDockElement? Model
{
get => _model;
set
{
if (_model == value) return;
DetachModel();
_model = value as DockLeaf;
AttachModel();
OnPropertyChanged(nameof(Model));
}
}
///
public LayoutManager? LayoutManager
{
get => _layoutManager;
set
{
if (_layoutManager == value) return;
_layoutManager = value;
OnPropertyChanged(nameof(LayoutManager));
}
}
///
public IDockContextManager? ContextManager
{
get => _contextManager;
set
{
if (_contextManager == value) return;
_contextManager = value;
OnPropertyChanged(nameof(ContextManager));
}
}
///
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected == value) return;
_isSelected = value;
OnPropertyChanged(nameof(IsSelected));
}
}
///
public bool IsActive
{
get => _isActive;
set
{
if (_isActive == value) return;
_isActive = value;
OnPropertyChanged(nameof(IsActive));
}
}
///
public TabPlacement TabPlacement
{
get => _tabPlacement;
set
{
if (_tabPlacement != value)
{
_tabPlacement = value;
UpdateTabPlacement();
OnPropertyChanged(nameof(TabPlacement));
}
}
}
///
public bool ShowCloseButtons
{
get => _showCloseButtons;
set
{
if (_showCloseButtons != value)
{
_showCloseButtons = value;
OnPropertyChanged(nameof(ShowCloseButtons));
UpdateTabHeaders();
}
}
}
///
public bool CanReorderTabs
{
get => _canReorderTabs;
set
{
if (_canReorderTabs != value)
{
_canReorderTabs = value;
OnPropertyChanged(nameof(CanReorderTabs));
}
}
}
///
public IDockContent? ActiveContent
{
get => _model?.ActiveContent;
set
{
if (_model != null)
{
_model.ActiveContent = value;
}
}
}
///
public event EventHandler? ActiveContentChanged;
///
public event EventHandler? ContentClosing;
///
public event EventHandler? TabsReordered;
///
public event PropertyChangedEventHandler? PropertyChanged;
///
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
_rootGrid = GetTemplateChild("PART_RootGrid") as Grid;
_tabHeaderList = GetTemplateChild("PART_TabHeaderList") as ListBox;
_contentControl = GetTemplateChild("PART_ContentControl") as ContentControl;
if (_tabHeaderList != null)
{
_tabHeaderList.SelectionChanged += OnTabSelectionChanged;
}
UpdateTabPlacement();
UpdateTabHeaders();
}
///
/// Обрабатывает изменение контекста данных контрола.
///
private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
Model = args.NewValue as DockLeaf;
}
///
/// Присоединяет модель данных к контролу.
///
private void AttachModel()
{
if (_model != null)
{
_model.PropertyChanged += _modelPropertyChangedHandler;
if (_model.Children is INotifyCollectionChanged notifyCollection)
{
notifyCollection.CollectionChanged += OnChildrenCollectionChanged;
}
this.DataContext = _model;
_tabPlacement = _model.TabPlacement;
UpdateTabHeaders();
UpdateTabPlacement();
}
}
///
/// Отсоединяет модель данных от контрола.
///
private void DetachModel()
{
if (_model != null)
{
_model.PropertyChanged -= _modelPropertyChangedHandler;
if (_model.Children is INotifyCollectionChanged notifyCollection)
{
notifyCollection.CollectionChanged -= OnChildrenCollectionChanged;
}
this.DataContext = null;
}
}
///
/// Обрабатывает изменения свойств модели данных.
///
private void OnModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(DockLeaf.TabPlacement):
_tabPlacement = _model?.TabPlacement ?? TabPlacement.Top;
OnPropertyChanged(nameof(TabPlacement));
UpdateTabPlacement();
break;
case nameof(DockLeaf.ActiveContent):
OnPropertyChanged(nameof(ActiveContent));
UpdateSelectedTab();
break;
case nameof(DockLeaf.Children):
UpdateTabHeaders();
break;
}
}
///
/// Обрабатывает изменения коллекции вкладок.
///
private void OnChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
UpdateTabHeaders();
}
///
/// Обновляет положение панели вкладок.
///
private void UpdateTabPlacement()
{
if (_rootGrid == null || _model == null) return;
_rootGrid.RowDefinitions.Clear();
_rootGrid.ColumnDefinitions.Clear();
switch (_model.TabPlacement)
{
case TabPlacement.Top:
_rootGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
_rootGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
UpdateHeaderListOrientation(Orientation.Horizontal);
break;
case TabPlacement.Bottom:
_rootGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
_rootGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
UpdateHeaderListOrientation(Orientation.Horizontal);
break;
case TabPlacement.Left:
_rootGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
_rootGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
UpdateHeaderListOrientation(Orientation.Vertical);
break;
case TabPlacement.Right:
_rootGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
_rootGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
UpdateHeaderListOrientation(Orientation.Vertical);
break;
}
UpdateElementPositions();
}
///
/// Обновляет ориентацию списка заголовков вкладок.
///
/// Новая ориентация списка.
private void UpdateHeaderListOrientation(Orientation orientation)
{
if (_tabHeaderList?.ItemsPanelRoot is StackPanel stackPanel)
{
stackPanel.Orientation = orientation;
}
}
///
/// Обновляет позиции элементов в сетке.
///
private void UpdateElementPositions()
{
if (_rootGrid == null || _tabHeaderList == null || _contentControl == null) return;
switch (_model?.TabPlacement)
{
case TabPlacement.Top:
Grid.SetRow(_tabHeaderList, 0);
Grid.SetRow(_contentControl, 1);
Grid.SetColumn(_tabHeaderList, 0);
Grid.SetColumn(_contentControl, 0);
break;
case TabPlacement.Bottom:
Grid.SetRow(_contentControl, 0);
Grid.SetRow(_tabHeaderList, 1);
Grid.SetColumn(_contentControl, 0);
Grid.SetColumn(_tabHeaderList, 0);
break;
case TabPlacement.Left:
Grid.SetColumn(_tabHeaderList, 0);
Grid.SetColumn(_contentControl, 1);
Grid.SetRow(_tabHeaderList, 0);
Grid.SetRow(_contentControl, 0);
break;
case TabPlacement.Right:
Grid.SetColumn(_contentControl, 0);
Grid.SetColumn(_tabHeaderList, 1);
Grid.SetRow(_contentControl, 0);
Grid.SetRow(_tabHeaderList, 0);
break;
}
}
///
/// Обновляет заголовки вкладок.
///
private void UpdateTabHeaders()
{
if (_tabHeaderList == null || _model == null) return;
_tabHeaderList.Items.Clear();
foreach (var content in _model.Children)
{
var item = CreateTabHeaderItem(content);
_tabHeaderList.Items.Add(item);
}
UpdateSelectedTab();
}
///
/// Создает элемент заголовка вкладки.
///
/// Содержимое вкладки.
/// Созданный элемент заголовка.
private ListBoxItem CreateTabHeaderItem(IDockContent content)
{
var item = new ListBoxItem
{
Content = CreateTabHeaderContent(content),
Tag = content,
HorizontalContentAlignment = HorizontalAlignment.Stretch,
VerticalContentAlignment = VerticalAlignment.Stretch
};
item.PointerPressed += (sender, e) =>
{
if (e.GetCurrentPoint(item).Properties.IsLeftButtonPressed)
{
ActiveContent = content;
e.Handled = true;
}
};
return item;
}
///
/// Создает содержимое заголовка вкладки.
///
/// Содержимое вкладки.
/// Созданное содержимое заголовка.
private UIElement CreateTabHeaderContent(IDockContent content)
{
var grid = new Grid();
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
var textBlock = new TextBlock
{
Text = content.Title,
Margin = new Thickness(8, 4, 8, 4),
VerticalAlignment = VerticalAlignment.Center
};
Grid.SetColumn(textBlock, 0);
grid.Children.Add(textBlock);
if (_showCloseButtons && content.CanClose)
{
var closeButton = new Button
{
Content = "×",
FontSize = 16,
Width = 24,
Height = 24,
Margin = new Thickness(2),
Padding = new Thickness(0),
Background = new SolidColorBrush(Microsoft.UI.Colors.Transparent),
BorderThickness = new Thickness(0)
};
closeButton.Click += (sender, e) =>
{
CloseContent(content);
e.Handled = true;
};
Grid.SetColumn(closeButton, 1);
grid.Children.Add(closeButton);
}
return grid;
}
///
/// Обновляет выбранную вкладку.
///
private void UpdateSelectedTab()
{
if (_tabHeaderList == null || _model == null) return;
foreach (var item in _tabHeaderList.Items)
{
if (item is ListBoxItem listBoxItem && listBoxItem.Tag is IDockContent content)
{
listBoxItem.IsSelected = content == _model.ActiveContent;
}
}
if (_contentControl != null)
{
_contentControl.Content = _model.ActiveContent?.View;
}
}
///
/// Обрабатывает изменение выбора вкладки.
///
private void OnTabSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_tabHeaderList?.SelectedItem is ListBoxItem selectedItem &&
selectedItem.Tag is IDockContent content)
{
var oldContent = ActiveContent;
ActiveContent = content;
if (oldContent != content)
{
ActiveContentChanged?.Invoke(this,
new ActiveContentChangedEventArgs(oldContent, content));
}
}
}
///
public void AddContent(IDockContent content)
{
if (_model != null && !_model.Children.Contains(content))
{
_model.AddContent(content);
UpdateTabHeaders();
}
}
///
public void RemoveContent(IDockContent content)
{
if (_model != null && _model.Children.Contains(content))
{
_model.RemoveContent(content);
UpdateTabHeaders();
}
}
///
public bool CloseContent(IDockContent content)
{
var args = new ContentClosingEventArgs(content);
ContentClosing?.Invoke(this, args);
if (!args.Cancel)
{
RemoveContent(content);
return true;
}
return false;
}
///
public void CloseAllExcept(IDockContent exceptContent)
{
if (_model == null) return;
var itemsToClose = _model.Children
.Where(c => c != exceptContent)
.ToList();
foreach (var content in itemsToClose)
{
CloseContent(content);
}
}
///
public void CloseAll()
{
if (_model == null) return;
var itemsToClose = _model.Children.ToList();
foreach (var content in itemsToClose)
{
CloseContent(content);
}
}
///
public void Refresh()
{
UpdateTabHeaders();
UpdateTabPlacement();
}
///
public void ApplyTheme(IDockTheme theme)
{
if (theme != null)
{
// TODO: Реализовать применение темы к стилям контрола
}
}
///
public void OnModelPropertyChanged(string propertyName)
{
if (_model != null)
{
OnModelPropertyChanged(_model, new PropertyChangedEventArgs(propertyName));
}
}
///
/// Вызывает событие изменения свойства.
///
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
///
public void Dispose()
{
if (!_disposed)
{
DetachModel();
if (_tabHeaderList != null)
{
_tabHeaderList.SelectionChanged -= OnTabSelectionChanged;
}
_disposed = true;
GC.SuppressFinalize(this);
}
}
}