feat(ui): migrate ProjectBrowser to GridView, improve cleanup

Refactored ProjectBrowser to use GridView instead of ItemsView for file display, updated selection logic, and set a minimum grid height. FloatingWindow now manages DockingLayout cleanup on close to prevent resource leaks. Simplified DockDocument and DockGroup instantiations in EngineEditorWindow. Updated GetDirectoryNameConverter to use Path.GetDirectoryName directly. App shutdown now calls EditorApplication.Shutdown(). Added Ghost.Engine reference in ActivationHandler.
This commit is contained in:
2026-03-29 19:55:05 +09:00
parent fa617accc3
commit b28b32f502
7 changed files with 44 additions and 42 deletions

View File

@@ -1,5 +1,6 @@
using Ghost.Editor.Core.Utilities; using Ghost.Editor.Core.Utilities;
using Ghost.Editor.Models; using Ghost.Editor.Models;
using Ghost.Engine;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using System.Reflection; using System.Reflection;
@@ -63,10 +64,7 @@ internal static class ActivationHandler
AllocationManager.Initialize(opts); AllocationManager.Initialize(opts);
TypeCache.Initialize(); TypeCache.Initialize();
// await ((Core.AssetHandle.AssetService)App.GetService<IAssetService>()).Init(); //App.GetService<EngineCore>();
// TODO: Init other subsystems here.
// await Task.Delay(10000); // Wait 10 seconds to simulate work.
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }

View File

@@ -151,7 +151,7 @@ public partial class App : Application
Host.StopAsync().GetAwaiter().GetResult(); Host.StopAsync().GetAwaiter().GetResult();
Host.Dispose(); Host.Dispose();
//EditorApplication.Shutdown(); EditorApplication.Shutdown();
ActivationHandler.Shutdown(); ActivationHandler.Shutdown();
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -6,7 +6,7 @@ public partial class GetDirectoryNameConverter : IValueConverter
{ {
public object? Convert(object value, Type targetType, object parameter, string language) public object? Convert(object value, Type targetType, object parameter, string language)
{ {
return value is string path ? System.IO.Path.GetDirectoryName(path) : null; return value is string path ? Path.GetDirectoryName(path) : null;
} }
public object? ConvertBack(object value, Type targetType, object parameter, string language) public object? ConvertBack(object value, Type targetType, object parameter, string language)

View File

@@ -7,6 +7,8 @@ namespace Ghost.Editor.View.Controls.Docking;
/// </summary> /// </summary>
public class FloatingWindow : Window public class FloatingWindow : Window
{ {
private readonly DockingLayout _layout;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FloatingWindow"/> class with the specified document. /// Initializes a new instance of the <see cref="FloatingWindow"/> class with the specified document.
/// </summary> /// </summary>
@@ -15,16 +17,26 @@ public class FloatingWindow : Window
{ {
ArgumentNullException.ThrowIfNull(document); ArgumentNullException.ThrowIfNull(document);
var layout = new DockingLayout(); _layout = new DockingLayout();
var group = new DockGroup(); var group = new DockGroup();
group.AddChild(document); group.AddChild(document);
layout.RootModule = group; _layout.RootModule = group;
layout.LayoutEmpty += (s, e) => Close(); _layout.LayoutEmpty += (s, e) => Close();
Content = layout; Content = _layout;
// Basic window setup // Basic window setup
AppWindow.Resize(new global::Windows.Graphics.SizeInt32(800, 600)); AppWindow.Resize(new global::Windows.Graphics.SizeInt32(800, 600));
// When the user manually closes the window, ensure we clean up the documents inside
this.Closed += FloatingWindow_Closed;
}
private void FloatingWindow_Closed(object sender, WindowEventArgs args)
{
// Force cleanup of the visual tree so we don't leak anything from this window
_layout.RootModule = null;
Content = null;
} }
} }

View File

@@ -17,7 +17,7 @@
<converter:ExplorerItemToIconUriConverter x:Key="ExplorerItemToIconUriConverter" /> <converter:ExplorerItemToIconUriConverter x:Key="ExplorerItemToIconUriConverter" />
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid MinHeight="50">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
@@ -137,19 +137,20 @@
<BreadcrumbBar /> <BreadcrumbBar />
</Border>--> </Border>-->
<ItemsView <GridView
x:Name="PART_FilesView" x:Name="PART_FilesView"
Grid.Row="0" Grid.Row="0"
Padding="8" Padding="8"
DoubleTapped="PART_FilesView_DoubleTapped" DoubleTapped="PART_FilesView_DoubleTapped"
IsDoubleTapEnabled="True" IsDoubleTapEnabled="True"
ItemsSource="{x:Bind ViewModel.Files, Mode=OneWay}" ItemsSource="{x:Bind ViewModel.Files}"
SelectionChanged="PART_FilesView_SelectionChanged" SelectionChanged="PART_FilesView_SelectionChanged"
SelectionMode="Single"> SelectionMode="Single">
<ItemsView.ItemTemplate> <GridView.ItemTemplate>
<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"
@@ -186,18 +187,11 @@
</Grid> </Grid>
</ItemContainer> </ItemContainer>
</DataTemplate> </DataTemplate>
</ItemsView.ItemTemplate> </GridView.ItemTemplate>
<ItemsView.Layout> <GridView.ContextFlyout>
<UniformGridLayout
ItemsStretch="None"
MinColumnSpacing="4"
MinItemWidth="72"
MinRowSpacing="4" />
</ItemsView.Layout>
<ItemsView.ContextFlyout>
<ghost:ContextFlyout Tag="project-browser" /> <ghost:ContextFlyout Tag="project-browser" />
</ItemsView.ContextFlyout> </GridView.ContextFlyout>
</ItemsView> </GridView>
<Border <Border
Grid.Row="1" Grid.Row="1"

View File

@@ -1,3 +1,4 @@
using CommunityToolkit.WinUI;
using Ghost.Editor.Core.Contracts; using Ghost.Editor.Core.Contracts;
using Ghost.Editor.Models; using Ghost.Editor.Models;
using Ghost.Editor.ViewModels.Controls; using Ghost.Editor.ViewModels.Controls;
@@ -49,9 +50,6 @@ 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;
// HACK: Scroll a little bit to trigger ScrollView to update it's scrollbar visibility. Otherwise the scrollbar will show a incorrect state when docking layout changes.
PART_FilesView.ScrollView.ScrollBy(1, 0);
} }
private void ProjectBrowser_Unloaded(object sender, RoutedEventArgs e) private void ProjectBrowser_Unloaded(object sender, RoutedEventArgs e)
@@ -92,7 +90,7 @@ internal sealed partial class ProjectBrowser : UserControl
_isUpdatingSelection = false; _isUpdatingSelection = false;
} }
private void PART_FilesView_SelectionChanged(ItemsView sender, ItemsViewSelectionChangedEventArgs args) private void PART_FilesView_SelectionChanged(object sender, SelectionChangedEventArgs args)
{ {
if (_isUpdatingSelection) if (_isUpdatingSelection)
{ {
@@ -139,7 +137,7 @@ internal sealed partial class ProjectBrowser : UserControl
else if (navigatedItem.Item2 == 1) else if (navigatedItem.Item2 == 1)
{ {
var index = ViewModel.Files.IndexOf(navigatedItem.Item1); var index = ViewModel.Files.IndexOf(navigatedItem.Item1);
PART_FilesView.Select(index); PART_FilesView.SelectedIndex = index;
} }
} }

View File

@@ -49,31 +49,31 @@ internal sealed partial class EngineEditorWindow : WindowEx
private void InitializeDockingLayout() private void InitializeDockingLayout()
{ {
var sceneDoc = new Ghost.Editor.View.Controls.Docking.DockDocument { Title = "Scene", Content = new Ghost.Editor.View.Pages.EngineEditor.ScenePage() }; var sceneDoc = new Controls.Docking.DockDocument { Title = "Scene", Content = new Pages.EngineEditor.ScenePage() };
var hierarchyDoc = new Ghost.Editor.View.Controls.Docking.DockDocument { Title = "Hierarchy", Content = new Ghost.Editor.View.Controls.Hierarchy() }; var hierarchyDoc = new Controls.Docking.DockDocument { Title = "Hierarchy", Content = new Controls.Hierarchy() };
var inspectorDoc = new Ghost.Editor.View.Controls.Docking.DockDocument { Title = "Inspector", Content = new Ghost.Editor.View.Pages.EngineEditor.InspectorPage() }; var inspectorDoc = new Controls.Docking.DockDocument { Title = "Inspector", Content = new Pages.EngineEditor.InspectorPage() };
var projectDoc = new Ghost.Editor.View.Controls.Docking.DockDocument { Title = "Project", Content = new Ghost.Editor.View.Controls.ProjectBrowser() }; var projectDoc = new Controls.Docking.DockDocument { Title = "Project", Content = new Controls.ProjectBrowser() };
var consoleDoc = new Ghost.Editor.View.Controls.Docking.DockDocument { Title = "Console", Content = new Ghost.Editor.View.Pages.EngineEditor.ConsolePage() }; var consoleDoc = new Controls.Docking.DockDocument { Title = "Console", Content = new Pages.EngineEditor.ConsolePage() };
var leftGroup = new Ghost.Editor.View.Controls.Docking.DockGroup(); var leftGroup = new Controls.Docking.DockGroup();
leftGroup.AddChild(hierarchyDoc); leftGroup.AddChild(hierarchyDoc);
var centerGroup = new Ghost.Editor.View.Controls.Docking.DockGroup(); var centerGroup = new Controls.Docking.DockGroup();
centerGroup.AddChild(sceneDoc); centerGroup.AddChild(sceneDoc);
var rightGroup = new Ghost.Editor.View.Controls.Docking.DockGroup(); var rightGroup = new Controls.Docking.DockGroup();
rightGroup.AddChild(inspectorDoc); rightGroup.AddChild(inspectorDoc);
var bottomGroup = new Ghost.Editor.View.Controls.Docking.DockGroup(); var bottomGroup = new Controls.Docking.DockGroup();
bottomGroup.AddChild(projectDoc); bottomGroup.AddChild(projectDoc);
bottomGroup.AddChild(consoleDoc); bottomGroup.AddChild(consoleDoc);
var topPanel = new Ghost.Editor.View.Controls.Docking.DockPanel { Orientation = Microsoft.UI.Xaml.Controls.Orientation.Horizontal }; var topPanel = new Controls.Docking.DockPanel { Orientation = Microsoft.UI.Xaml.Controls.Orientation.Horizontal };
topPanel.AddChild(leftGroup); topPanel.AddChild(leftGroup);
topPanel.AddChild(centerGroup); topPanel.AddChild(centerGroup);
topPanel.AddChild(rightGroup); topPanel.AddChild(rightGroup);
var rootPanel = new Ghost.Editor.View.Controls.Docking.DockPanel { Orientation = Microsoft.UI.Xaml.Controls.Orientation.Vertical }; var rootPanel = new Controls.Docking.DockPanel { Orientation = Microsoft.UI.Xaml.Controls.Orientation.Vertical };
rootPanel.AddChild(topPanel); rootPanel.AddChild(topPanel);
rootPanel.AddChild(bottomGroup); rootPanel.AddChild(bottomGroup);