refactor: adopt WinUI.Dock approach for layout updates and tab content hosting
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
<ResourceDictionary Source="/View/Controls/Docking/DockGroup.xaml" />
|
<ResourceDictionary Source="/View/Controls/Docking/DockGroup.xaml" />
|
||||||
<ResourceDictionary Source="/View/Controls/Docking/DockPanel.xaml" />
|
<ResourceDictionary Source="/View/Controls/Docking/DockPanel.xaml" />
|
||||||
<ResourceDictionary Source="/View/Controls/Docking/DockRegionHighlight.xaml" />
|
<ResourceDictionary Source="/View/Controls/Docking/DockRegionHighlight.xaml" />
|
||||||
|
<ResourceDictionary Source="/View/Controls/Docking/DockDocument.xaml" />
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:Ghost.Editor.View.Controls.Docking">
|
||||||
|
|
||||||
|
<Style TargetType="local:DockDocument">
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="local:DockDocument">
|
||||||
|
<Border Background="Transparent">
|
||||||
|
<ContentPresenter Content="{TemplateBinding Content}"
|
||||||
|
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
|
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -115,70 +115,41 @@ public partial class DockGroup : DockContainer
|
|||||||
|
|
||||||
var selectedDoc = _tabView.SelectedItem is TabViewItem selectedItem ? selectedItem.Tag as DockDocument : null;
|
var selectedDoc = _tabView.SelectedItem is TabViewItem selectedItem ? selectedItem.Tag as DockDocument : null;
|
||||||
|
|
||||||
// Remove tabs that are no longer in Children
|
// Detach all existing tabs
|
||||||
for (int i = _tabView.TabItems.Count - 1; i >= 0; i--)
|
foreach (var item in _tabView.TabItems)
|
||||||
{
|
{
|
||||||
if (_tabView.TabItems[i] is TabViewItem tabItem && tabItem.Tag is DockDocument doc)
|
if (item is TabViewItem tabItem)
|
||||||
{
|
{
|
||||||
if (!Children.Contains(doc))
|
|
||||||
{
|
|
||||||
tabItem.ClearValue(ContentControl.ContentProperty);
|
|
||||||
tabItem.Content = null;
|
tabItem.Content = null;
|
||||||
_tabView.TabItems.RemoveAt(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_tabView.TabItems.Clear();
|
||||||
|
|
||||||
TabViewItem? newSelectedItem = null;
|
TabViewItem? newSelectedItem = null;
|
||||||
|
|
||||||
// Add tabs that are in Children but not in TabItems, and ensure correct order
|
foreach (var child in Children)
|
||||||
for (int i = 0; i < Children.Count; i++)
|
|
||||||
{
|
{
|
||||||
if (Children[i] is DockDocument doc)
|
if (child is DockDocument doc)
|
||||||
{
|
{
|
||||||
TabViewItem? existingTab = null;
|
var tabItem = new TabViewItem
|
||||||
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
|
Tag = doc
|
||||||
};
|
};
|
||||||
|
|
||||||
existingTab.SetBinding(TabViewItem.HeaderProperty, new Binding
|
tabItem.SetBinding(TabViewItem.HeaderProperty, new Microsoft.UI.Xaml.Data.Binding
|
||||||
{
|
{
|
||||||
Source = doc,
|
Source = doc,
|
||||||
Path = new PropertyPath(nameof(DockDocument.Title)),
|
Path = new PropertyPath(nameof(DockDocument.Title)),
|
||||||
Mode = BindingMode.OneWay
|
Mode = Microsoft.UI.Xaml.Data.BindingMode.OneWay
|
||||||
});
|
});
|
||||||
|
|
||||||
existingTab.SetBinding(ContentControl.ContentProperty, new Binding
|
tabItem.Content = doc;
|
||||||
{
|
|
||||||
Source = doc,
|
|
||||||
Path = new PropertyPath(nameof(DockDocument.Content)),
|
|
||||||
Mode = BindingMode.OneWay
|
|
||||||
});
|
|
||||||
|
|
||||||
_tabView.TabItems.Insert(i, existingTab);
|
_tabView.TabItems.Add(tabItem);
|
||||||
}
|
|
||||||
|
|
||||||
if (doc == selectedDoc)
|
if (doc == selectedDoc)
|
||||||
{
|
{
|
||||||
newSelectedItem = existingTab;
|
newSelectedItem = tabItem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using CommunityToolkit.WinUI.Controls;
|
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using CommunityToolkit.WinUI.Controls;
|
||||||
|
|
||||||
namespace Ghost.Editor.View.Controls.Docking;
|
namespace Ghost.Editor.View.Controls.Docking;
|
||||||
|
|
||||||
@@ -8,10 +8,10 @@ namespace Ghost.Editor.View.Controls.Docking;
|
|||||||
/// A container that can host multiple dock modules with splitters.
|
/// A container that can host multiple dock modules with splitters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TemplatePart(Name = PART_GRID, Type = typeof(Grid))]
|
[TemplatePart(Name = PART_GRID, Type = typeof(Grid))]
|
||||||
public partial class DockPanel : DockContainer
|
public class DockPanel : DockContainer
|
||||||
{
|
{
|
||||||
private const string PART_GRID = "PART_Grid";
|
private const string PART_GRID = "PART_Grid";
|
||||||
private const double SPLITTER_THICKNESS = 1;
|
private const double SPLITTER_THICKNESS = 4;
|
||||||
|
|
||||||
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
|
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
|
||||||
nameof(Orientation), typeof(Orientation), typeof(DockPanel), new PropertyMetadata(Orientation.Horizontal, OnOrientationChanged));
|
nameof(Orientation), typeof(Orientation), typeof(DockPanel), new PropertyMetadata(Orientation.Horizontal, OnOrientationChanged));
|
||||||
@@ -74,123 +74,44 @@ public partial class DockPanel : DockContainer
|
|||||||
{
|
{
|
||||||
if (_grid == null) return;
|
if (_grid == null) return;
|
||||||
|
|
||||||
// Remove splitters and children that are no longer in the collection
|
_grid.Children.Clear();
|
||||||
for (int i = _grid.Children.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
var child = _grid.Children[i];
|
|
||||||
if (child is GridSplitter)
|
|
||||||
{
|
|
||||||
_grid.Children.RemoveAt(i);
|
|
||||||
}
|
|
||||||
else if (child is DockModule module && !Children.Contains(module))
|
|
||||||
{
|
|
||||||
_grid.Children.RemoveAt(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Children.Count == 0)
|
|
||||||
{
|
|
||||||
_grid.RowDefinitions.Clear();
|
_grid.RowDefinitions.Clear();
|
||||||
_grid.ColumnDefinitions.Clear();
|
_grid.ColumnDefinitions.Clear();
|
||||||
return;
|
|
||||||
}
|
if (Children.Count == 0) return;
|
||||||
|
|
||||||
if (Orientation == Orientation.Horizontal)
|
if (Orientation == Orientation.Horizontal)
|
||||||
{
|
{
|
||||||
_grid.RowDefinitions.Clear();
|
for (int i = 0; i < Children.Count; i++)
|
||||||
|
|
||||||
int requiredColumns = (Children.Count * 2) - 1;
|
|
||||||
while (_grid.ColumnDefinitions.Count > requiredColumns)
|
|
||||||
{
|
|
||||||
_grid.ColumnDefinitions.RemoveAt(_grid.ColumnDefinitions.Count - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < Children.Count; i++)
|
|
||||||
{
|
|
||||||
int columnIndex = i * 2;
|
|
||||||
if (columnIndex >= _grid.ColumnDefinitions.Count)
|
|
||||||
{
|
{
|
||||||
_grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
_grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_grid.ColumnDefinitions[columnIndex].Width = new GridLength(1, GridUnitType.Star);
|
|
||||||
}
|
|
||||||
|
|
||||||
var child = Children[i];
|
var child = Children[i];
|
||||||
if (!_grid.Children.Contains(child))
|
Grid.SetColumn(child, i * 2);
|
||||||
{
|
|
||||||
_grid.Children.Add(child);
|
_grid.Children.Add(child);
|
||||||
}
|
|
||||||
|
|
||||||
Grid.SetColumn(child, columnIndex);
|
|
||||||
Grid.SetRow(child, 0);
|
|
||||||
|
|
||||||
if (i < Children.Count - 1)
|
if (i < Children.Count - 1)
|
||||||
{
|
|
||||||
int splitterIndex = i * 2 + 1;
|
|
||||||
if (splitterIndex >= _grid.ColumnDefinitions.Count)
|
|
||||||
{
|
{
|
||||||
_grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
_grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_grid.ColumnDefinitions[splitterIndex].Width = GridLength.Auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
var splitter = new GridSplitter { ResizeDirection = GridSplitter.GridResizeDirection.Columns, Width = SPLITTER_THICKNESS };
|
var splitter = new GridSplitter { ResizeDirection = GridSplitter.GridResizeDirection.Columns, Width = SPLITTER_THICKNESS };
|
||||||
Grid.SetColumn(splitter, splitterIndex);
|
Grid.SetColumn(splitter, i * 2 + 1);
|
||||||
Grid.SetRow(splitter, 0);
|
|
||||||
_grid.Children.Add(splitter);
|
_grid.Children.Add(splitter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_grid.ColumnDefinitions.Clear();
|
for (int i = 0; i < Children.Count; i++)
|
||||||
|
|
||||||
int requiredRows = (Children.Count * 2) - 1;
|
|
||||||
while (_grid.RowDefinitions.Count > requiredRows)
|
|
||||||
{
|
|
||||||
_grid.RowDefinitions.RemoveAt(_grid.RowDefinitions.Count - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < Children.Count; i++)
|
|
||||||
{
|
|
||||||
int rowIndex = i * 2;
|
|
||||||
if (rowIndex >= _grid.RowDefinitions.Count)
|
|
||||||
{
|
{
|
||||||
_grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
_grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_grid.RowDefinitions[rowIndex].Height = new GridLength(1, GridUnitType.Star);
|
|
||||||
}
|
|
||||||
|
|
||||||
var child = Children[i];
|
var child = Children[i];
|
||||||
if (!_grid.Children.Contains(child))
|
Grid.SetRow(child, i * 2);
|
||||||
{
|
|
||||||
_grid.Children.Add(child);
|
_grid.Children.Add(child);
|
||||||
}
|
|
||||||
|
|
||||||
Grid.SetRow(child, rowIndex);
|
|
||||||
Grid.SetColumn(child, 0);
|
|
||||||
|
|
||||||
if (i < Children.Count - 1)
|
if (i < Children.Count - 1)
|
||||||
{
|
|
||||||
int splitterIndex = i * 2 + 1;
|
|
||||||
if (splitterIndex >= _grid.RowDefinitions.Count)
|
|
||||||
{
|
{
|
||||||
_grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
_grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_grid.RowDefinitions[splitterIndex].Height = GridLength.Auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
var splitter = new GridSplitter { ResizeDirection = GridSplitter.GridResizeDirection.Rows, Height = SPLITTER_THICKNESS };
|
var splitter = new GridSplitter { ResizeDirection = GridSplitter.GridResizeDirection.Rows, Height = SPLITTER_THICKNESS };
|
||||||
Grid.SetRow(splitter, splitterIndex);
|
Grid.SetRow(splitter, i * 2 + 1);
|
||||||
Grid.SetColumn(splitter, 0);
|
|
||||||
_grid.Children.Add(splitter);
|
_grid.Children.Add(splitter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,6 @@
|
|||||||
<DataTemplate x:DataType="model:ExplorerItem">
|
<DataTemplate x:DataType="model:ExplorerItem">
|
||||||
<ItemContainer>
|
<ItemContainer>
|
||||||
<Grid
|
<Grid
|
||||||
Width="72"
|
|
||||||
Padding="8"
|
Padding="8"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
@@ -192,7 +191,7 @@
|
|||||||
</ItemsView.ItemTemplate>
|
</ItemsView.ItemTemplate>
|
||||||
<ItemsView.Layout>
|
<ItemsView.Layout>
|
||||||
<UniformGridLayout
|
<UniformGridLayout
|
||||||
ItemsStretch="None"
|
ItemsStretch="Fill"
|
||||||
MinColumnSpacing="4"
|
MinColumnSpacing="4"
|
||||||
MinItemWidth="72"
|
MinItemWidth="72"
|
||||||
MinRowSpacing="4" />
|
MinRowSpacing="4" />
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ internal sealed partial class ProjectBrowser : UserControl
|
|||||||
|
|
||||||
private readonly IInspectorService _inspectorService;
|
private readonly IInspectorService _inspectorService;
|
||||||
private bool _isUpdatingSelection = false;
|
private bool _isUpdatingSelection = false;
|
||||||
private double _savedHorizontalOffset;
|
|
||||||
private double _savedVerticalOffset;
|
|
||||||
private ScrollViewer? _filesScrollViewer;
|
|
||||||
|
|
||||||
public ProjectBrowserViewModel ViewModel
|
public ProjectBrowserViewModel ViewModel
|
||||||
{
|
{
|
||||||
@@ -52,43 +49,12 @@ internal sealed partial class ProjectBrowser : UserControl
|
|||||||
private void ProjectBrowser_Loaded(object sender, RoutedEventArgs e)
|
private void ProjectBrowser_Loaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_inspectorService.OnSelectionChanged += _inspectorService_OnSelectionChanged;
|
_inspectorService.OnSelectionChanged += _inspectorService_OnSelectionChanged;
|
||||||
PART_FilesView.UpdateLayout();
|
|
||||||
|
|
||||||
DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
|
|
||||||
{
|
|
||||||
if (_filesScrollViewer == null)
|
|
||||||
{
|
|
||||||
_filesScrollViewer = FindVisualChild<ScrollViewer>(PART_FilesView);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_filesScrollViewer != null)
|
|
||||||
{
|
|
||||||
// Sometimes resetting the ItemsSource helps clear corrupted virtualization state
|
|
||||||
var itemsSource = PART_FilesView.ItemsSource;
|
|
||||||
PART_FilesView.ItemsSource = null;
|
|
||||||
PART_FilesView.ItemsSource = itemsSource;
|
|
||||||
|
|
||||||
PART_FilesView.UpdateLayout();
|
|
||||||
_filesScrollViewer.ChangeView(_savedHorizontalOffset, _savedVerticalOffset, null, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProjectBrowser_Unloaded(object sender, RoutedEventArgs e)
|
private void ProjectBrowser_Unloaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_inspectorService.OnSelectionChanged -= _inspectorService_OnSelectionChanged;
|
_inspectorService.OnSelectionChanged -= _inspectorService_OnSelectionChanged;
|
||||||
|
|
||||||
if (_filesScrollViewer == null)
|
|
||||||
{
|
|
||||||
_filesScrollViewer = FindVisualChild<ScrollViewer>(PART_FilesView);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_filesScrollViewer != null)
|
|
||||||
{
|
|
||||||
_savedHorizontalOffset = _filesScrollViewer.HorizontalOffset;
|
|
||||||
_savedVerticalOffset = _filesScrollViewer.VerticalOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LastFocused == this)
|
if (LastFocused == this)
|
||||||
{
|
{
|
||||||
LastFocused = null;
|
LastFocused = null;
|
||||||
@@ -104,26 +70,6 @@ internal sealed partial class ProjectBrowser : UserControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static T? FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
|
|
||||||
{
|
|
||||||
for (int i = 0; i < Microsoft.UI.Xaml.Media.VisualTreeHelper.GetChildrenCount(parent); i++)
|
|
||||||
{
|
|
||||||
var child = Microsoft.UI.Xaml.Media.VisualTreeHelper.GetChild(parent, i);
|
|
||||||
if (child is T t)
|
|
||||||
{
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = FindVisualChild<T>(child);
|
|
||||||
if (result != null)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PART_DirectoriesView_SelectionChanged(TreeView sender, TreeViewSelectionChangedEventArgs args)
|
private void PART_DirectoriesView_SelectionChanged(TreeView sender, TreeViewSelectionChangedEventArgs args)
|
||||||
{
|
{
|
||||||
if (_isUpdatingSelection)
|
if (_isUpdatingSelection)
|
||||||
|
|||||||
Reference in New Issue
Block a user