Refactor folder structure

This commit is contained in:
2026-02-18 00:50:46 +09:00
parent 426786397c
commit db8ca971a8
413 changed files with 2885 additions and 3634 deletions

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="Ghost.Editor.View.Controls.Hierarchy"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Ghost.Editor.View.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid
Grid.Row="0"
Height="40"
Padding="8,8,8,4"
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<AutoSuggestBox
Grid.Column="0"
PlaceholderText="Search"
QueryIcon="Find" />
<StackPanel Grid.Column="1" Orientation="Horizontal">
<AppBarSeparator />
<Button Style="{ThemeResource ToolbarButton}">
<FontIcon FontSize="{StaticResource ToolbarIconSize}" Glyph="&#xE8F4;" />
</Button>
</StackPanel>
</Grid>
<Border Grid.Row="1" Padding="4">
<ListView>
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon FontSize="{StaticResource ToolbarIconSize}" Glyph="&#xF159;" />
<TextBlock Text="Test" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon FontSize="{StaticResource ToolbarIconSize}" Glyph="&#xF159;" />
<TextBlock Text="Test" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon FontSize="{StaticResource ToolbarIconSize}" Glyph="&#xF159;" />
<TextBlock Text="Test" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon FontSize="{StaticResource ToolbarIconSize}" Glyph="&#xF159;" />
<TextBlock Text="Test" />
</StackPanel>
</ListView>
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,27 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace Ghost.Editor.View.Controls;
public sealed partial class Hierarchy : UserControl
{
public Hierarchy()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,53 @@
using Ghost.Editor.Core;
namespace Ghost.Editor.View.Controls;
internal partial class ProjectBrowser
{
[ContextMenuItem("project-browser", "Show in Explorer")]
private static void ShowInExplorer()
{
var path = LastFocused?.ViewModel.CurrentDirectoryPath;
if (!Directory.Exists(path))
{
return;
}
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo()
{
FileName = path,
UseShellExecute = true,
Verb = "open"
});
}
[ContextMenuItem("project-browser", "Create/Folder")]
private static void CreateFolder()
{
// TODO: Use AssetService
var viewModel = LastFocused?.ViewModel;
if (viewModel is null)
{
return;
}
var currentDir = viewModel.CurrentDirectoryPath;
if (!Directory.Exists(currentDir))
{
return;
}
var newFolderPath = Path.Combine(currentDir, "New Folder");
var folderIndex = 1;
while (Directory.Exists(newFolderPath))
{
newFolderPath = Path.Combine(currentDir, $"New Folder ({folderIndex})");
folderIndex++;
}
Directory.CreateDirectory(newFolderPath);
// Refresh the view model to show the new folder
viewModel.NavigateToDirectory(currentDir);
}
}

View File

@@ -0,0 +1,216 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="Ghost.Editor.View.Controls.ProjectBrowser"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:community="using:CommunityToolkit.WinUI.Controls"
xmlns:converter="using:Ghost.Editor.Utilities.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ghost="using:Ghost.Editor.Core.Controls"
xmlns:local="using:Ghost.Editor.View.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:Ghost.Editor.Models"
xmlns:sys="using:System"
mc:Ignorable="d">
<UserControl.Resources>
<converter:ExplorerItemToIconUriConverter x:Key="ExplorerItemToIconUriConverter" />
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Toolbar -->
<Grid
Height="36"
Padding="4"
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Button Style="{ThemeResource ToolbarButton}">
<Button.Content>
<FontIcon FontSize="{StaticResource ToolbarIconSize}" Glyph="&#xE710;" />
</Button.Content>
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem Text="Folder" />
<MenuFlyoutItem Text="Script" />
<MenuFlyoutSubItem Text="Rendering">
<MenuFlyoutItem Text="Material" />
<MenuFlyoutItem Text="Volume Profile" />
</MenuFlyoutSubItem>
</MenuFlyout>
</Button.Flyout>
</Button>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<AutoSuggestBox
Width="250"
PlaceholderText="Search"
QueryIcon="Find" />
<AppBarSeparator />
<Button Style="{ThemeResource ToolbarButton}">
<Button.Content>
<FontIcon FontSize="{StaticResource ToolbarIconSize}" Glyph="&#xE97C;" />
</Button.Content>
<Button.Flyout>
<MenuFlyout>
<ToggleMenuFlyoutItem Text="Animation" />
<ToggleMenuFlyoutItem Text="Audio" />
<ToggleMenuFlyoutItem Text="Material" />
<ToggleMenuFlyoutItem Text="Script" />
<ToggleMenuFlyoutItem Text="Texture" />
</MenuFlyout>
</Button.Flyout>
</Button>
<Button Style="{ThemeResource ToolbarButton}">
<Button.Content>
<FontIcon FontSize="{StaticResource ToolbarIconSize}" Glyph="&#xE8EC;" />
</Button.Content>
</Button>
</StackPanel>
</Grid>
<!-- Conent Viewer -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Grid.Column="0"
Width="200"
Padding="4,0,0,0"
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
BorderThickness="0,0,1,0">
<TreeView
x:Name="PART_DirectoriesView"
ItemsSource="{x:Bind ViewModel.Directories, Mode=OneWay}"
SelectionChanged="PART_DirectoriesView_SelectionChanged"
SelectionMode="Single">
<TreeView.ItemTemplate>
<DataTemplate x:DataType="model:ExplorerItem">
<TreeViewItem ItemsSource="{x:Bind Children}">
<StackPanel Orientation="Horizontal" Spacing="4">
<!-- TODO: Open/Close folder icon based on state -->
<FontIcon
VerticalAlignment="Center"
FontSize="12"
Glyph="&#xE8B7;" />
<TextBlock
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Name}"
TextTrimming="CharacterEllipsis" />
</StackPanel>
</TreeViewItem>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Border>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<!--<RowDefinition Height="Auto" />-->
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!--<Border
Grid.Row="0"
Height="24"
Background="{ThemeResource LayerFillColorDefaultBrush}"
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1">
<BreadcrumbBar />
</Border>-->
<ItemsView
x:Name="PART_FilesView"
Grid.Row="0"
Padding="8"
DoubleTapped="PART_FilesView_DoubleTapped"
IsDoubleTapEnabled="True"
ItemsSource="{x:Bind ViewModel.Files, Mode=OneWay}"
SelectionChanged="PART_FilesView_SelectionChanged"
SelectionMode="Single">
<ItemsView.ItemTemplate>
<DataTemplate x:DataType="model:ExplorerItem">
<ItemContainer>
<Grid
Padding="8"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RowSpacing="4"
ToolTipService.ToolTip="{x:Bind Name}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="Open" />
<MenuFlyoutItem Text="Rename" />
<MenuFlyoutItem Text="Delete" />
<MenuFlyoutItem Text="Show in Explorer" />
</MenuFlyout>
</Grid.ContextFlyout>
<community:ConstrainedBox Grid.Row="0" AspectRatio="1:1">
<Image HorizontalAlignment="Center">
<Image.Source>
<BitmapImage DecodePixelWidth="48" UriSource="{x:Bind Converter={StaticResource ExplorerItemToIconUriConverter}}" />
</Image.Source>
</Image>
</community:ConstrainedBox>
<TextBlock
Grid.Row="1"
HorizontalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Name}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
</Grid>
</ItemContainer>
</DataTemplate>
</ItemsView.ItemTemplate>
<ItemsView.Layout>
<UniformGridLayout
ItemsStretch="Fill"
MinColumnSpacing="4"
MinItemWidth="72"
MinRowSpacing="4" />
</ItemsView.Layout>
<ItemsView.ContextFlyout>
<ghost:ContextFlyout Tag="project-browser" />
</ItemsView.ContextFlyout>
</ItemsView>
<Border
Grid.Row="1"
Height="24"
Padding="8,2"
Background="{ThemeResource LayerFillColorDefaultBrush}"
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
BorderThickness="0,1,0,0">
<StackPanel>
<TextBlock Text="{x:Bind sys:String.Format('{0} items', ViewModel.Files.Count), Mode=OneWay}" />
</StackPanel>
</Border>
</Grid>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,146 @@
using Ghost.Editor.Core.Contracts;
using Ghost.Editor.Models;
using Ghost.Editor.ViewModels.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
namespace Ghost.Editor.View.Controls;
internal sealed partial class ProjectBrowser : UserControl
{
public static ProjectBrowser? LastFocused
{
get;
private set;
}
private readonly IInspectorService _inspectorService;
private bool _isUpdatingSelection = false;
public ProjectBrowserViewModel ViewModel
{
get;
}
public ProjectBrowser()
{
_inspectorService = App.GetService<IInspectorService>();
ViewModel = App.GetService<ProjectBrowserViewModel>();
InitializeComponent();
Loaded += ProjectBrowser_Loaded;
Unloaded += ProjectBrowser_Unloaded;
GettingFocus += ProjectBrowser_GettingFocus;
}
private void ProjectBrowser_GettingFocus(UIElement sender, GettingFocusEventArgs args)
{
if (_isUpdatingSelection)
{
return;
}
LastFocused = this;
}
private void ProjectBrowser_Loaded(object sender, RoutedEventArgs e)
{
_inspectorService.OnSelectionChanged += _inspectorService_OnSelectionChanged;
}
private void ProjectBrowser_Unloaded(object sender, RoutedEventArgs e)
{
_inspectorService.OnSelectionChanged -= _inspectorService_OnSelectionChanged;
if (LastFocused == this)
{
LastFocused = null;
}
}
private void _inspectorService_OnSelectionChanged(object? sender, InspectorSelectionChangedEventArgs e)
{
if (e.Source is not ProjectBrowserViewModel)
{
PART_FilesView.DeselectAll();
PART_DirectoriesView.SelectedNodes.Clear();
}
}
private void PART_DirectoriesView_SelectionChanged(TreeView sender, TreeViewSelectionChangedEventArgs args)
{
if (_isUpdatingSelection)
{
return;
}
_isUpdatingSelection = true;
PART_FilesView.DeselectAll();
if (args.AddedItems.Count > 0 && args.AddedItems[0] is ExplorerItem selectedItem)
{
ViewModel.SelectedItem = selectedItem;
ViewModel.NavigateToDirectory(selectedItem.FullName);
}
_isUpdatingSelection = false;
}
private void PART_FilesView_SelectionChanged(ItemsView sender, ItemsViewSelectionChangedEventArgs args)
{
if (_isUpdatingSelection)
{
return;
}
_isUpdatingSelection = true;
PART_DirectoriesView.SelectedNodes.Clear();
if (PART_FilesView.SelectedItem is ExplorerItem selectedItem)
{
ViewModel.SelectedItem = selectedItem;
}
_isUpdatingSelection = false;
}
private async void PART_FilesView_DoubleTapped(object sender, Microsoft.UI.Xaml.Input.DoubleTappedRoutedEventArgs e)
{
if (PART_FilesView.SelectedItem is ExplorerItem selectedItem)
{
_isUpdatingSelection = true;
PART_FilesView.DeselectAll();
PART_DirectoriesView.SelectedNodes.Clear();
// NOTE: There is bug that the hover state of the item may remain when navigating to another folder.
// Which causes incorrect selection (double click a folder -> directories view select target folder -> files view select the item that has the same index as the double clicked one and the hover visual stay remain from last level)
// Not sure if this is a WinUI bug or something else. This may because of the virtualization of the ItemsView.
// The core issue is not sure why PART_FilesView_SelectionChanged been triggered after NavigateToDirectory is already finished. And this only happens after the first double click navigation (first time is always fine).
// HACK: Wait a moment to let the ui clear it's state, otherwise the bug above will happen because of the virtualization.
await Task.Delay(100);
ViewModel.SelectedItem = selectedItem;
var navigatedItem = ViewModel.OpenSelected();
if (navigatedItem.Item1 != null)
{
if (navigatedItem.Item2 == 0)
{
PART_DirectoriesView.SelectedItem = navigatedItem.Item1;
}
else if (navigatedItem.Item2 == 1)
{
var index = ViewModel.Files.IndexOf(navigatedItem.Item1);
PART_FilesView.Select(index);
}
}
_isUpdatingSelection = false;
}
}
}