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; 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 bool CanDrag => true; public bool CanDrop => true; 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 object? PrepareDragData() => Model; public bool HandleDrop(object data, DockPosition position) => false; 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; } }; 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); }; 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) { // 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); } } }