using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Data; namespace Ghost.Editor.Views.Controls.Docking; /// /// A container that displays its children (documents) as tabs. /// [TemplatePart(Name = PART_TAB_VIEW, Type = typeof(TabView))] public partial class DockGroup : DockContainer { private const string PART_TAB_VIEW = "PART_TabView"; private const string DRAG_DOCUMENT_KEY = "DockDocument"; private TabView? _tabView; public DockGroup() { DefaultStyleKey = typeof(DockGroup); } protected override void ValidateChild(DockModule module) { base.ValidateChild(module); if (module is not DockDocument) { throw new ArgumentException($"{nameof(DockGroup)} only accepts {nameof(DockDocument)} children.", nameof(module)); } } protected override void OnApplyTemplate() { base.OnApplyTemplate(); if (_tabView != null) { _tabView.TabDragStarting -= OnTabDragStarting; _tabView.TabDroppedOutside -= OnTabDroppedOutside; _tabView.DragOver -= OnDragOver; _tabView.Drop -= OnDrop; _tabView.DragLeave -= OnDragLeave; _tabView.TabCloseRequested -= OnTabCloseRequested; } _tabView = GetTemplateChild(PART_TAB_VIEW) as TabView; if (_tabView != null) { _tabView.TabDragStarting += OnTabDragStarting; _tabView.TabDroppedOutside += OnTabDroppedOutside; _tabView.DragOver += OnDragOver; _tabView.Drop += OnDrop; _tabView.DragLeave += OnDragLeave; _tabView.TabCloseRequested += OnTabCloseRequested; } UpdateTabs(); } private void OnTabDragStarting(TabView sender, TabViewTabDragStartingEventArgs args) { if (args.Tab.Tag is DockDocument doc) { args.Data.Properties.Add(DRAG_DOCUMENT_KEY, doc); } } private void OnTabDroppedOutside(TabView sender, TabViewTabDroppedOutsideEventArgs args) { if (args.Tab.Tag is DockDocument doc) { Root?.CreateFloatingWindow(doc); } } private void OnDragOver(object sender, DragEventArgs e) { if (e.DataView.Properties.ContainsKey(DRAG_DOCUMENT_KEY)) { e.AcceptedOperation = global::Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move; Root?.ShowHighlight(this, e.GetPosition(this)); } } private void OnDrop(object sender, DragEventArgs e) { if (e.DataView.Properties.TryGetValue(DRAG_DOCUMENT_KEY, out var obj) && obj is DockDocument doc) { Root?.HandleDrop(doc, this, e.GetPosition(this)); } } private void OnDragLeave(object sender, DragEventArgs e) { Root?.HideHighlight(); } private void OnTabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args) { if (args.Tab.Tag is DockDocument doc) { RemoveChild(doc); } } protected override void OnChildrenUpdated() { UpdateTabs(); } private void UpdateTabs() { if (_tabView == null || Root == null) return; var selectedDoc = _tabView.SelectedItem is TabViewItem selectedItem ? selectedItem.Tag as DockDocument : null; // Remove tabs that are no longer in Children for (int i = _tabView.TabItems.Count - 1; i >= 0; i--) { if (_tabView.TabItems[i] is TabViewItem tabItem && tabItem.Tag is DockDocument doc) { if (!Children.Contains(doc)) { tabItem.Content = null; _tabView.TabItems.RemoveAt(i); } } } // Add new tabs that aren't in TabItems yet, and ensure correct order for (int i = 0; i < Children.Count; i++) { if (Children[i] is DockDocument doc) { TabViewItem? existingTab = null; for (int j = 0; j < _tabView.TabItems.Count; j++) { if (_tabView.TabItems[j] is TabViewItem tabItem && tabItem.Tag == doc) { existingTab = tabItem; // Fix order if necessary if (j != i) { _tabView.TabItems.RemoveAt(j); _tabView.TabItems.Insert(i, existingTab); } break; } } if (existingTab == null) { existingTab = new TabViewItem { Tag = doc, Content = doc }; existingTab.SetBinding(TabViewItem.HeaderProperty, new Binding { Source = doc, Path = new PropertyPath(nameof(DockDocument.Title)), Mode = Microsoft.UI.Xaml.Data.BindingMode.OneWay }); _tabView.TabItems.Insert(i, existingTab); } } } // Restore selection if (selectedDoc != null && _tabView.TabItems.FirstOrDefault(t => t is TabViewItem item && item.Tag == selectedDoc) is TabViewItem newSelected) { _tabView.SelectedItem = newSelected; } else if (_tabView.TabItems.Count > 0) { _tabView.SelectedItem = _tabView.TabItems[0]; } } }