diff --git a/projects/Oxygen.Editor.WorldEditor/src/Oxygen.Editor.WorldEditor.csproj b/projects/Oxygen.Editor.WorldEditor/src/Oxygen.Editor.WorldEditor.csproj index a7125870..564ec55c 100644 --- a/projects/Oxygen.Editor.WorldEditor/src/Oxygen.Editor.WorldEditor.csproj +++ b/projects/Oxygen.Editor.WorldEditor/src/Oxygen.Editor.WorldEditor.csproj @@ -8,6 +8,8 @@ 10.0.22000.0 x86;x64;arm64 win-x86;win-x64;win-arm64 + true + True @@ -61,38 +66,4 @@ - - - MSBuild:Compile - - - - - - MSBuild:Compile - - - - - - MSBuild:Compile - - - - - - MSBuild:Compile - - - - - - - - - - MSBuild:Compile - - - diff --git a/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/EntityAdapter.cs b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/EntityAdapter.cs new file mode 100644 index 00000000..7d10bb40 --- /dev/null +++ b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/EntityAdapter.cs @@ -0,0 +1,26 @@ +// Distributed under the MIT License. See accompanying file LICENSE or copy +// at https://opensource.org/licenses/MIT. +// SPDX-License-Identifier: MIT + +namespace Oxygen.Editor.WorldEditor.ProjectExplorer; + +using DroidNet.Controls; +using Oxygen.Editor.Projects; + +/// +/// A item adapter for the model class. +/// +/// The object to wrap as a . +public partial class EntityAdapter(Entity entity) : TreeItemAdapter, ITreeItem +{ + public override bool IsRoot => false; + + public override string Label + => this.AttachedObject.Name; + + public Entity AttachedObject => entity; + + protected override int GetChildrenCount() => 0; + + protected override async Task LoadChildren() => await Task.CompletedTask.ConfigureAwait(false); +} diff --git a/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ProjectAdapter.cs b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ProjectAdapter.cs new file mode 100644 index 00000000..64fef383 --- /dev/null +++ b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ProjectAdapter.cs @@ -0,0 +1,44 @@ +// Distributed under the MIT License. See accompanying file LICENSE or copy +// at https://opensource.org/licenses/MIT. +// SPDX-License-Identifier: MIT + +namespace Oxygen.Editor.WorldEditor.ProjectExplorer; + +using DroidNet.Controls; +using Oxygen.Editor.Projects; + +/// +/// A item adapter for the model class. +/// +/// The object to wrap as a . +/// The configured project manager service. +public partial class ProjectAdapter(Project project, IProjectManagerService projectManager) + : TreeItemAdapter, ITreeItem +{ + public override string Label => project.ProjectInfo.Name; + + public Project AttachedObject => project; + + protected override int GetChildrenCount() => project.Scenes.Count; + + protected override async Task LoadChildren() + { + this.ClearChildren(); + + if (!await projectManager.LoadProjectScenesAsync(project).ConfigureAwait(false)) + { + return; + } + + foreach (var scene in project.Scenes) + { + this.AddChildInternal( + new SceneAdapter(scene, projectManager) + { + IsExpanded = true, + }); + } + + await Task.CompletedTask.ConfigureAwait(true); + } +} diff --git a/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ProjectExplorerView.xaml b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ProjectExplorerView.xaml new file mode 100644 index 00000000..c13cd8d9 --- /dev/null +++ b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ProjectExplorerView.xaml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ProjectExplorerView.xaml.cs b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ProjectExplorerView.xaml.cs new file mode 100644 index 00000000..ee894a0a --- /dev/null +++ b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ProjectExplorerView.xaml.cs @@ -0,0 +1,62 @@ +// Distributed under the MIT License. See accompanying file LICENSE or copy +// at https://opensource.org/licenses/MIT. +// SPDX-License-Identifier: MIT + +namespace Oxygen.Editor.WorldEditor.ProjectExplorer; + +using DroidNet.Controls; +using DroidNet.Mvvm.Generators; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Input; +using Oxygen.Editor.Projects; + +/// +/// A View that shows a hierarchical layout of a project that has scenes, which +/// in turn can hold multiple entities. It demonstrates the flexibility of the +/// in representing hierarchical layouts of mixed types which can be loaded dynamically. +/// +[ViewModel(typeof(ProjectExplorerViewModel))] +public sealed partial class ProjectExplorerView +{ + public ProjectExplorerView() + { + this.InitializeComponent(); + + this.Loaded += this.OnLoaded; + } + + private async void OnLoaded(object sender, RoutedEventArgs args) + { + _ = sender; // unused + _ = args; // unused + + if (this.ViewModel is not null) + { + await this.ViewModel.LoadProjectCommand.ExecuteAsync(parameter: null).ConfigureAwait(true); + } + } + + private void UndoInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + _ = sender; // unused + args.Handled = true; + + this.ViewModel!.UndoCommand.Execute(parameter: null); + } + + private void RedoInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + _ = sender; // unused + args.Handled = true; + + this.ViewModel!.RedoCommand.Execute(parameter: null); + } + + private async void DeleteInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + _ = sender; // unused + args.Handled = true; + + await this.ViewModel!.RemoveSelectedItemsCommand.ExecuteAsync(parameter: null).ConfigureAwait(false); + } +} diff --git a/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ProjectExplorerViewModel.cs b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ProjectExplorerViewModel.cs new file mode 100644 index 00000000..0dcd71ab --- /dev/null +++ b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ProjectExplorerViewModel.cs @@ -0,0 +1,262 @@ +// Distributed under the MIT License. See accompanying file LICENSE or copy +// at https://opensource.org/licenses/MIT. +// SPDX-License-Identifier: MIT + +namespace Oxygen.Editor.WorldEditor.ProjectExplorer; + +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using CommunityToolkit.Mvvm.Input; +using DroidNet.Controls; +using DroidNet.TimeMachine; +using DroidNet.TimeMachine.Changes; +using Oxygen.Editor.Projects; +using Oxygen.Editor.WorldEditor.Views; + +/// +/// The ViewModel for the view. +/// +public partial class ProjectExplorerViewModel : DynamicTreeViewModel +{ + private readonly IProjectManagerService projectManager; + + public ProjectExplorerViewModel(IProjectManagerService projectManager) + { + this.projectManager = projectManager; + + // TODO: subscribe to CurrentProject property changes + + this.UndoStack = UndoRedo.Default[this].UndoStack; + this.RedoStack = UndoRedo.Default[this].RedoStack; + + this.ItemBeingRemoved += this.OnItemBeingRemoved; + this.ItemRemoved += this.OnItemRemoved; + + this.ItemBeingAdded += this.OnItemBeingAdded; + this.ItemAdded += this.OnItemAdded; + } + + public ProjectAdapter? Project { get; private set; } + + public ReadOnlyObservableCollection UndoStack { get; } + + public ReadOnlyObservableCollection RedoStack { get; } + + private bool HasUnlockedSelectedItems { get; set; } + + protected override void OnSelectionModelChanged(SelectionModel? oldValue) + { + base.OnSelectionModelChanged(oldValue); + + if (oldValue is not null) + { + oldValue.PropertyChanged -= this.SelectionModel_OnPropertyChanged; + } + + if (this.SelectionModel is not null) + { + this.SelectionModel.PropertyChanged += this.SelectionModel_OnPropertyChanged; + } + } + + [RelayCommand(CanExecute = nameof(HasUnlockedSelectedItems))] + protected override async Task RemoveSelectedItems() + { + UndoRedo.Default[this].BeginChangeSet($"Remove {this.SelectionModel}"); + await base.RemoveSelectedItems().ConfigureAwait(false); + UndoRedo.Default[this].EndChangeSet(); + } + + private void OnItemAdded(object? sender, ItemAddedEventArgs args) + { + _ = sender; // unused + + UndoRedo.Default[this] + .AddChange( + $"RemoveItem({args.TreeItem.Label})", + () => this.RemoveItem(args.TreeItem).GetAwaiter().GetResult()); + } + + private void OnItemRemoved(object? sender, ItemRemovedEventArgs args) + { + _ = sender; // unused + + UndoRedo.Default[this] + .AddChange( + $"Add Item({args.TreeItem.Label})", + () => this.AddItem(args.Parent, args.TreeItem).GetAwaiter().GetResult()); + } + + [RelayCommand] + private void Undo() => UndoRedo.Default[this].Undo(); + + [RelayCommand] + private void Redo() => UndoRedo.Default[this].Redo(); + + private void SelectionModel_OnPropertyChanged(object? sender, PropertyChangedEventArgs args) + { + if (!string.Equals(args.PropertyName, nameof(SelectionModel.IsEmpty), StringComparison.Ordinal)) + { + return; + } + + this.AddEntityCommand.NotifyCanExecuteChanged(); + + var hasUnlockedSelectedItems = false; + switch (this.SelectionModel) + { + case SingleSelectionModel: + hasUnlockedSelectedItems = this.SelectionModel.SelectedItem?.IsLocked == false; + break; + + case MultipleSelectionModel multipleSelectionModel: + foreach (var selectedIndex in multipleSelectionModel.SelectedIndices) + { + var item = this.ShownItems[selectedIndex]; + hasUnlockedSelectedItems = !item.IsLocked; + if (hasUnlockedSelectedItems) + { + break; + } + } + + break; + + default: + // Keep it false + break; + } + + if (hasUnlockedSelectedItems != this.HasUnlockedSelectedItems) + { + this.HasUnlockedSelectedItems = hasUnlockedSelectedItems; + this.RemoveSelectedItemsCommand.NotifyCanExecuteChanged(); + } + } + + private void OnItemBeingAdded(object? sender, ItemBeingAddedEventArgs args) + { + _ = sender; // unused + + switch (args.TreeItem) + { + case SceneAdapter sceneAdapter: + { + var scene = sceneAdapter.AttachedObject; + var parentAdapter = args.Parent as ProjectAdapter; + Debug.Assert(parentAdapter is not null, "the parent of a SceneAdpater must be a ProjectAdapter"); + var project = parentAdapter.AttachedObject; + project.Scenes.Add(scene); + break; + } + + case EntityAdapter entityAdapter: + { + var entity = entityAdapter.AttachedObject; + var parentAdapter = args.Parent as SceneAdapter; + Debug.Assert(parentAdapter is not null, "the parent of a EntityAdapter must be a SceneAdapter"); + var scene = parentAdapter.AttachedObject; + scene.Entities.Add(entity); + break; + } + + default: + // Do nothing + break; + } + } + + private void OnItemBeingRemoved(object? sender, ItemBeingRemovedEventArgs args) + { + _ = sender; // unused + + Debug.Assert(args.TreeItem.Parent is not null, "any item in the tree should have a parent"); + switch (args.TreeItem) + { + case SceneAdapter sceneAdapter: + { + var scene = sceneAdapter.AttachedObject; + var parentAdapter = sceneAdapter.Parent as ProjectAdapter; + Debug.Assert(parentAdapter is not null, "the parent of a SceneAdpater must be a ProjectAdapter"); + var project = parentAdapter.AttachedObject; + project.Scenes.Remove(scene); + break; + } + + case EntityAdapter entityAdapter: + { + var entity = entityAdapter.AttachedObject; + var parentAdapter = entityAdapter.Parent as SceneAdapter; + Debug.Assert(parentAdapter is not null, "the parent of a EntityAdapter must be a SceneAdapter"); + var scene = parentAdapter.AttachedObject; + scene.Entities.Remove(entity); + break; + } + + default: + // Do nothing + break; + } + } + + [RelayCommand] + private async Task LoadProjectAsync() + { + var currentProject = this.projectManager.CurrentProject; + if (currentProject is not Projects.Project project) + { + this.Project = null; + return; + } + + this.Project = new ProjectAdapter(project, this.projectManager); + await this.InitializeRootAsync(this.Project).ConfigureAwait(false); + } + + [RelayCommand] + private async Task AddScene() + { + if (this.Project is null) + { + return; + } + + var newScene = new SceneAdapter( + new Scene($"New Scene {this.Project.AttachedObject.Scenes.Count}", this.Project.AttachedObject), + this.projectManager); + + await this.AddItem(this.Project, newScene).ConfigureAwait(false); + } + + private bool CanAddEntity() + => (this.SelectionModel is SingleSelectionModel && this.SelectionModel.SelectedIndex != -1) || + this.SelectionModel is MultipleSelectionModel { SelectedIndices.Count: 1 }; + + [RelayCommand(CanExecute = nameof(CanAddEntity))] + private async Task AddEntity() + { + var selectedItem = this.SelectionModel?.SelectedItem; + if (selectedItem is null) + { + return; + } + + var scene = selectedItem switch + { + SceneAdapter item => item, + EntityAdapter { Parent: SceneAdapter } entity => (SceneAdapter)entity.Parent, + + // Anything else is not a valid item to which we can add an entity + _ => null, + }; + + if (scene is null) + { + return; + } + + var newEntity = new EntityAdapter(new Entity($"New Entity {scene.AttachedObject.Entities.Count}")); + await this.AddItem(scene, newEntity).ConfigureAwait(false); + } +} diff --git a/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/SceneAdapter.cs b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/SceneAdapter.cs new file mode 100644 index 00000000..ffec284d --- /dev/null +++ b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/SceneAdapter.cs @@ -0,0 +1,46 @@ +// Distributed under the MIT License. See accompanying file LICENSE or copy +// at https://opensource.org/licenses/MIT. +// SPDX-License-Identifier: MIT + +namespace Oxygen.Editor.WorldEditor.ProjectExplorer; + +using DroidNet.Controls; +using Oxygen.Editor.Projects; + +/// +/// A item adapter for the model class. +/// +/// The object to wrap as a . +/// The configured project manager service. +public partial class SceneAdapter(Scene scene, IProjectManagerService projectManager) + : TreeItemAdapter, ITreeItem +{ + public override bool IsRoot => false; + + public override string Label => scene.Name; + + public Scene AttachedObject => scene; + + protected override int GetChildrenCount() => scene.Entities.Count; + + protected override async Task LoadChildren() + { + this.ClearChildren(); + + if (!await projectManager.LoadSceneEntitiesAsync(scene).ConfigureAwait(false)) + { + return; + } + + foreach (var entity in this.AttachedObject.Entities) + { + this.AddChildInternal( + new EntityAdapter(entity) + { + IsExpanded = false, + }); + } + + await Task.CompletedTask.ConfigureAwait(true); + } +} diff --git a/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/Styles.xaml b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/Styles.xaml new file mode 100644 index 00000000..105497ef --- /dev/null +++ b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/Styles.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/Styles.xaml.cs b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/Styles.xaml.cs new file mode 100644 index 00000000..5a08d882 --- /dev/null +++ b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/Styles.xaml.cs @@ -0,0 +1,17 @@ +// Distributed under the MIT License. See accompanying file LICENSE or copy +// at https://opensource.org/licenses/MIT. +// SPDX-License-Identifier: MIT + +namespace Oxygen.Editor.WorldEditor.ProjectExplorer; + +using DroidNet.Controls; +using Microsoft.UI.Xaml; + +/// +/// A for the styles used in the demo. Because the +/// styles use {x:Bind}, they must be backed by a implemented in code behind. +/// +public partial class Styles +{ + public Styles() => this.InitializeComponent(); +} diff --git a/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ThumbnailGenerator.cs b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ThumbnailGenerator.cs new file mode 100644 index 00000000..60ee3007 --- /dev/null +++ b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ThumbnailGenerator.cs @@ -0,0 +1,59 @@ +// Distributed under the MIT License. See accompanying file LICENSE or copy +// at https://opensource.org/licenses/MIT. +// SPDX-License-Identifier: MIT + +namespace Oxygen.Editor.WorldEditor.ProjectExplorer; + +using System.Runtime.InteropServices.WindowsRuntime; +using DroidNet.Controls; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media.Imaging; + +/* + Pathless casting + ---------------- + The native bind parser doesn't provide a keyword to represent this as a function parameter, but it does support + pathless casting(for example, { x: Bind(x: String)}), which can be used as a function parameter.Therefore, {x:Bind + MethodName((namespace:TypeOfThis))} is a valid way to perform what is conceptually equivalent to {x:Bind + MethodName(this)}. + + The following works fine to use the current template item to call the function: + + + + +*/ + +public static class ThumbnailGenerator +{ + public static WriteableBitmap GenerateRandomImage(int width, int height) + { + var bitmap = new WriteableBitmap(width, height); + + using var stream = bitmap.PixelBuffer.AsStream(); + var pixels = new byte[width * height * 4]; // RGBA + var rand = new Random(); + + for (var i = 0; i < pixels.Length; i += 4) + { + pixels[i] = (byte)rand.Next(256); // R + pixels[i + 1] = (byte)rand.Next(256); // G + pixels[i + 2] = (byte)rand.Next(256); // B + pixels[i + 3] = 255; // A + } + + stream.Write(pixels, 0, pixels.Length); + + return bitmap; + } + + public static Symbol GetThumbnailForEntity(TreeItemAdapter adapter) + { + if (adapter is EntityAdapter entityAdapter && entityAdapter.AttachedObject.Name.EndsWith('1')) + { + return Symbol.Home; + } + + return Symbol.Admin; + } +} diff --git a/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ThumbnailTemplateSelector.cs b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ThumbnailTemplateSelector.cs new file mode 100644 index 00000000..52e16a9c --- /dev/null +++ b/projects/Oxygen.Editor.WorldEditor/src/ProjectExplorer/ThumbnailTemplateSelector.cs @@ -0,0 +1,48 @@ +// Distributed under the MIT License. See accompanying file LICENSE or copy +// at https://opensource.org/licenses/MIT. +// SPDX-License-Identifier: MIT + +namespace Oxygen.Editor.WorldEditor.ProjectExplorer; + +using DroidNet.Controls; +using Microsoft.UI.Xaml.Controls; +using DataTemplate = Microsoft.UI.Xaml.DataTemplate; +using DependencyObject = Microsoft.UI.Xaml.DependencyObject; + +/// +/// A that can map a to a template that can be used to display a +/// for it. +/// +public partial class ThumbnailTemplateSelector : DataTemplateSelector +{ +#if false + public ThumbnailTemplateSelector() + { + const string xaml = """ + + + + """; + + this.DefaultTemplate = (DataTemplate)XamlReader.Load(xaml); + } +#endif + + public DataTemplate? SceneTemplate { get; set; } + + public DataTemplate? EntityTemplate { get; set; } + + public DataTemplate? DefaultTemplate { get; set; } + + protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container) + => item switch + { + SceneAdapter => this.SceneTemplate, + EntityAdapter => this.EntityTemplate, + _ => this.DefaultTemplate, + }; +} diff --git a/projects/Oxygen.Editor.WorldEditor/src/ViewModels/ProjectExplorerViewModel.cs b/projects/Oxygen.Editor.WorldEditor/src/ViewModels/ProjectExplorerViewModel.cs deleted file mode 100644 index e9f904c4..00000000 --- a/projects/Oxygen.Editor.WorldEditor/src/ViewModels/ProjectExplorerViewModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Distributed under the MIT License. See accompanying file LICENSE or copy -// at https://opensource.org/licenses/MIT. -// SPDX-License-Identifier: MIT - -namespace Oxygen.Editor.WorldEditor.ViewModels; - -using DroidNet.Hosting.Generators; -using Microsoft.Extensions.DependencyInjection; - -[InjectAs(ServiceLifetime.Transient)] -public class ProjectExplorerViewModel; diff --git a/projects/Oxygen.Editor.WorldEditor/src/ViewModels/WorkspaceViewModel.cs b/projects/Oxygen.Editor.WorldEditor/src/ViewModels/WorkspaceViewModel.cs index fd4a841f..41ad2ac5 100644 --- a/projects/Oxygen.Editor.WorldEditor/src/ViewModels/WorkspaceViewModel.cs +++ b/projects/Oxygen.Editor.WorldEditor/src/ViewModels/WorkspaceViewModel.cs @@ -12,6 +12,7 @@ namespace Oxygen.Editor.WorldEditor.ViewModels; using DryIoc; using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Xaml; +using Oxygen.Editor.WorldEditor.ProjectExplorer; /// /// The view model for the World Editor main view. diff --git a/projects/Oxygen.Editor.WorldEditor/src/Views/ProjectExplorerView.xaml b/projects/Oxygen.Editor.WorldEditor/src/Views/ProjectExplorerView.xaml deleted file mode 100644 index 6c7222d3..00000000 --- a/projects/Oxygen.Editor.WorldEditor/src/Views/ProjectExplorerView.xaml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/projects/Oxygen.Editor.WorldEditor/src/Views/ProjectExplorerView.xaml.cs b/projects/Oxygen.Editor.WorldEditor/src/Views/ProjectExplorerView.xaml.cs deleted file mode 100644 index 96b57aab..00000000 --- a/projects/Oxygen.Editor.WorldEditor/src/Views/ProjectExplorerView.xaml.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Distributed under the MIT License. See accompanying file LICENSE or copy -// at https://opensource.org/licenses/MIT. -// SPDX-License-Identifier: MIT - -namespace Oxygen.Editor.WorldEditor.Views; - -using DroidNet.Hosting.Generators; -using DroidNet.Mvvm.Generators; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.UI.Xaml.Controls; -using Oxygen.Editor.WorldEditor.ViewModels; - -[ViewModel(typeof(ProjectExplorerViewModel))] -[InjectAs(ServiceLifetime.Transient)] -public sealed partial class ProjectExplorerView : UserControl -{ - public ProjectExplorerView() => this.InitializeComponent(); -} diff --git a/projects/Oxygen.Editor/src/Program.cs b/projects/Oxygen.Editor/src/Program.cs index 4de706b2..6a5d2ddd 100644 --- a/projects/Oxygen.Editor/src/Program.cs +++ b/projects/Oxygen.Editor/src/Program.cs @@ -33,9 +33,15 @@ namespace Oxygen.Editor; using Oxygen.Editor.ProjectBrowser.Projects; using Oxygen.Editor.ProjectBrowser.Templates; using Oxygen.Editor.ProjectBrowser.ViewModels; +using Oxygen.Editor.Projects; +using Oxygen.Editor.Projects.Config; +using Oxygen.Editor.Projects.Storage; using Oxygen.Editor.Services; using Oxygen.Editor.Shell; +using Oxygen.Editor.Storage; using Oxygen.Editor.Storage.Native; +using Oxygen.Editor.WorldEditor.ProjectExplorer; +using Oxygen.Editor.WorldEditor.Views; using Serilog; using Serilog.Events; using Serilog.Templates; @@ -178,14 +184,18 @@ private static void RegisterApplicationServices(HostBuilderContext context, DryI serviceKey: Uri.UriSchemeFile); sp.Container.Register(Reuse.Singleton); + // TODO: use keyed registration and parameter name to key mappings + // https://github.com/dadhi/DryIoc/blob/master/docs/DryIoc.Docs/SpecifyDependencyAndPrimitiveValues.md#complete-example-of-matching-the-parameter-name-to-the-service-key + sp.Container.Register(Reuse.Singleton); sp.Container.Register(Reuse.Singleton); sp.Container.Register(Reuse.Singleton); sp.Container.Register(Reuse.Singleton); + sp.Container.Register(Reuse.Singleton); // Register the project instance using a delegate that will request the currently open project from the project // browser service. sp.Container.RegisterDelegate( - resolverContext => resolverContext.Resolve().CurrentProject); + resolverContext => resolverContext.Resolve().CurrentProject); /* * Set up the view model to view converters. We're using the standard converter, and a custom one with fall back @@ -210,6 +220,8 @@ private static void RegisterApplicationServices(HostBuilderContext context, DryI // TODO(abdes): refactor into extension method sp.Container.Register(Reuse.Transient); sp.Container.Register(Reuse.Transient); + sp.Container.Register(Reuse.Transient); + sp.Container.Register(Reuse.Transient); } private static Routes MakeRoutes() => new( @@ -274,14 +286,20 @@ private static void AddConfigurationFiles(HostBuilderContext context, IConfigura var projectBrowserConfigPath = Path.GetFullPath( $"{Assembly.GetAssembly(typeof(ProjectBrowserSettings))!.GetName().Name}/Config/ProjectBrowser.config.json", Finder.ProgramData); + var categoriesConfigPath = Path.GetFullPath( + $"{Assembly.GetAssembly(typeof(ProjectsSettings))!.GetName().Name}/Config/Categories.config.json", + Finder.ProgramData); + _ = config.AddJsonFile(localSettingsPath, optional: true) - .AddJsonFile(projectBrowserConfigPath); + .AddJsonFile(projectBrowserConfigPath) + .AddJsonFile(categoriesConfigPath); } private static void ConfigureOptionsPattern(HostBuilderContext context, IServiceCollection sc) { _ = sc.Configure( context.Configuration.GetSection(ProjectBrowserSettings.ConfigSectionName)); + _ = sc.Configure(context.Configuration.GetSection(ProjectsSettings.ConfigSectionName)); _ = sc.ConfigureWritable( context.Configuration.GetSection(nameof(ThemeSettings)), Path.Combine(Finder.LocalAppData, "LocalSettings.json"));