diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 7ef74f1d1..6a02aa2ec 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -22,7 +22,8 @@ namespace Artemis.Core /// /// The parent of the folder /// The name of the folder - public Folder(ProfileElement parent, string name) : base(parent.Profile) + /// The order where to place the child (0-based), defaults to the end of the collection + public Folder(ProfileElement parent, string name, int order) : base(parent.Profile) { FolderEntity = new FolderEntity(); EntityId = Guid.NewGuid(); @@ -31,7 +32,7 @@ namespace Artemis.Core Profile = Parent.Profile; Name = name; - Parent.AddChild(this); + Parent.AddChild(this, order); } /// diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index dfb0a68dc..b1ccccfc6 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -29,7 +29,8 @@ namespace Artemis.Core /// /// The parent of the layer /// The name of the layer - public Layer(ProfileElement parent, string name) : base(parent.Profile) + /// The order where to place the child (0-based), defaults to the end of the collection + public Layer(ProfileElement parent, string name, int order) : base(parent.Profile) { LayerEntity = new LayerEntity(); EntityId = Guid.NewGuid(); @@ -47,7 +48,7 @@ namespace Artemis.Core Adapter = new LayerAdapter(this); Initialize(); - Parent.AddChild(this, 0); + Parent.AddChild(this, order); } /// diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index a9090e12d..a905c3017 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -198,7 +198,7 @@ namespace Artemis.Core FolderEntity? rootFolder = ProfileEntity.Folders.FirstOrDefault(f => f.ParentId == EntityId); if (rootFolder == null) { - Folder _ = new(this, "Root folder"); + Folder _ = new(this, "Root folder", 0); } else { diff --git a/src/Artemis.Core/Utilities/Utilities.cs b/src/Artemis.Core/Utilities/Utilities.cs index e6391399a..42774d5d9 100644 --- a/src/Artemis.Core/Utilities/Utilities.cs +++ b/src/Artemis.Core/Utilities/Utilities.cs @@ -44,6 +44,9 @@ namespace Artemis.Core /// A list of extra arguments to pass to Artemis when restarting public static void Restart(bool elevate, TimeSpan delay, params string[] extraArgs) { + if (!OperatingSystem.IsWindows() && elevate) + throw new ArtemisCoreException("Elevation on non-Windows platforms is not supported."); + OnRestartRequested(new RestartEventArgs(elevate, delay, extraArgs.ToList())); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs index 58d6560fd..d467c25a2 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs @@ -137,7 +137,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem if (!SupportsChildren) throw new ArtemisUIException("Cannot add a folder to a profile element of type " + ProfileElement.GetType().Name); - Folder _ = new(ProfileElement, "New folder"); + Folder _ = new(ProfileElement, "New folder", 0); _profileEditorService.SaveSelectedProfileConfiguration(); } @@ -146,7 +146,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem if (!SupportsChildren) throw new ArtemisUIException("Cannot add a layer to a profile element of type " + ProfileElement.GetType().Name); - Layer layer = new(ProfileElement, "New layer"); + Layer layer = new(ProfileElement, "New layer", 0); // Could be null if the default brush got disabled LayerBrushDescriptor brush = _layerBrushService.GetDefaultLayerBrush(); diff --git a/src/Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj b/src/Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj index 69e1b2744..95b799e45 100644 --- a/src/Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj +++ b/src/Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Avalonia/Artemis.UI.Linux/packages.lock.json b/src/Avalonia/Artemis.UI.Linux/packages.lock.json index bb01b4430..251ccdd08 100644 --- a/src/Avalonia/Artemis.UI.Linux/packages.lock.json +++ b/src/Avalonia/Artemis.UI.Linux/packages.lock.json @@ -53,6 +53,20 @@ "System.Reactive": "5.0.0" } }, + "ReactiveUI": { + "type": "Direct", + "requested": "[17.1.9, )", + "resolved": "17.1.9", + "contentHash": "bxH6uzEi1b6cfGoBBvCXWrdT18+Rleggi0R/vOaCYc+zvlSleJW6wlUtcWjrKzgQHD8EN3c02A4JBTt9oGSWWQ==", + "dependencies": { + "DynamicData": "7.4.3", + "Splat": "14.1.1", + "System.ComponentModel": "4.3.0", + "System.Diagnostics.Contracts": "4.3.0", + "System.Dynamic.Runtime": "4.3.0", + "System.Runtime.Serialization.Primitives": "4.3.0" + } + }, "Avalonia.Angle.Windows.Natives": { "type": "Transitive", "resolved": "2.1.0.2020091801", @@ -191,8 +205,8 @@ }, "DynamicData": { "type": "Transitive", - "resolved": "7.3.1", - "contentHash": "E9oTvWlAgzct0MuWt6k+0s+nSDA3LkFVvDwkMUTklIZZnva314KZAEF2vG4XX9I98ia+EpMqjte67jWEBJlsRw==", + "resolved": "7.4.3", + "contentHash": "7eGyREbtzyaRutMa+iToi2e41JboEVK9c1ZBcTvJOfEoTRIZX3hChIsxIvV0ErzMXtGHAIS2O0I8jLDUIds5wg==", "dependencies": { "System.Reactive": "5.0.0" } @@ -522,15 +536,6 @@ "Ninject": "3.3.3" } }, - "ReactiveUI": { - "type": "Transitive", - "resolved": "16.2.6", - "contentHash": "jf1RvD8HxHuA6CGQtheGHUCHzRrhpvo0z593Npsz7g8KJWXfGR45Dc9bILJHoymBxhdDD1L1WjUfh0fcucIPPg==", - "dependencies": { - "DynamicData": "7.3.1", - "Splat": "13.1.1" - } - }, "ReactiveUI.Validation": { "type": "Transitive", "resolved": "2.2.1", @@ -918,6 +923,14 @@ "System.Text.Encoding": "4.3.0" } }, + "System.Diagnostics.Contracts": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "eelRRbnm+OloiQvp9CXS0ixjNQldjjkHO4iIkR5XH2VIP8sUB/SIpa1TdUW6/+HDcQ+MlhP3pNa1u5SbzYuWGA==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", @@ -1398,6 +1411,15 @@ "System.Runtime.Extensions": "4.3.0" } }, + "System.Runtime.Serialization.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", + "dependencies": { + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0" + } + }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", @@ -1757,6 +1779,7 @@ "Material.Icons.Avalonia": "1.0.2", "RGB.NET.Core": "1.0.0-prerelease1", "RGB.NET.Layout": "1.0.0-prerelease1", + "ReactiveUI": "17.1.9", "ReactiveUI.Validation": "2.2.1", "Splat.Ninject": "14.1.17" } @@ -1774,6 +1797,7 @@ "FluentAvaloniaUI": "1.1.8", "Material.Icons.Avalonia": "1.0.2", "RGB.NET.Core": "1.0.0-prerelease1", + "ReactiveUI": "17.1.9", "ReactiveUI.Validation": "2.2.1" } } diff --git a/src/Avalonia/Artemis.UI.MacOS/Artemis.UI.MacOS.csproj b/src/Avalonia/Artemis.UI.MacOS/Artemis.UI.MacOS.csproj index 69e1b2744..95b799e45 100644 --- a/src/Avalonia/Artemis.UI.MacOS/Artemis.UI.MacOS.csproj +++ b/src/Avalonia/Artemis.UI.MacOS/Artemis.UI.MacOS.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Avalonia/Artemis.UI.MacOS/packages.lock.json b/src/Avalonia/Artemis.UI.MacOS/packages.lock.json index bb01b4430..251ccdd08 100644 --- a/src/Avalonia/Artemis.UI.MacOS/packages.lock.json +++ b/src/Avalonia/Artemis.UI.MacOS/packages.lock.json @@ -53,6 +53,20 @@ "System.Reactive": "5.0.0" } }, + "ReactiveUI": { + "type": "Direct", + "requested": "[17.1.9, )", + "resolved": "17.1.9", + "contentHash": "bxH6uzEi1b6cfGoBBvCXWrdT18+Rleggi0R/vOaCYc+zvlSleJW6wlUtcWjrKzgQHD8EN3c02A4JBTt9oGSWWQ==", + "dependencies": { + "DynamicData": "7.4.3", + "Splat": "14.1.1", + "System.ComponentModel": "4.3.0", + "System.Diagnostics.Contracts": "4.3.0", + "System.Dynamic.Runtime": "4.3.0", + "System.Runtime.Serialization.Primitives": "4.3.0" + } + }, "Avalonia.Angle.Windows.Natives": { "type": "Transitive", "resolved": "2.1.0.2020091801", @@ -191,8 +205,8 @@ }, "DynamicData": { "type": "Transitive", - "resolved": "7.3.1", - "contentHash": "E9oTvWlAgzct0MuWt6k+0s+nSDA3LkFVvDwkMUTklIZZnva314KZAEF2vG4XX9I98ia+EpMqjte67jWEBJlsRw==", + "resolved": "7.4.3", + "contentHash": "7eGyREbtzyaRutMa+iToi2e41JboEVK9c1ZBcTvJOfEoTRIZX3hChIsxIvV0ErzMXtGHAIS2O0I8jLDUIds5wg==", "dependencies": { "System.Reactive": "5.0.0" } @@ -522,15 +536,6 @@ "Ninject": "3.3.3" } }, - "ReactiveUI": { - "type": "Transitive", - "resolved": "16.2.6", - "contentHash": "jf1RvD8HxHuA6CGQtheGHUCHzRrhpvo0z593Npsz7g8KJWXfGR45Dc9bILJHoymBxhdDD1L1WjUfh0fcucIPPg==", - "dependencies": { - "DynamicData": "7.3.1", - "Splat": "13.1.1" - } - }, "ReactiveUI.Validation": { "type": "Transitive", "resolved": "2.2.1", @@ -918,6 +923,14 @@ "System.Text.Encoding": "4.3.0" } }, + "System.Diagnostics.Contracts": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "eelRRbnm+OloiQvp9CXS0ixjNQldjjkHO4iIkR5XH2VIP8sUB/SIpa1TdUW6/+HDcQ+MlhP3pNa1u5SbzYuWGA==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", @@ -1398,6 +1411,15 @@ "System.Runtime.Extensions": "4.3.0" } }, + "System.Runtime.Serialization.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", + "dependencies": { + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0" + } + }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", @@ -1757,6 +1779,7 @@ "Material.Icons.Avalonia": "1.0.2", "RGB.NET.Core": "1.0.0-prerelease1", "RGB.NET.Layout": "1.0.0-prerelease1", + "ReactiveUI": "17.1.9", "ReactiveUI.Validation": "2.2.1", "Splat.Ninject": "14.1.17" } @@ -1774,6 +1797,7 @@ "FluentAvaloniaUI": "1.1.8", "Material.Icons.Avalonia": "1.0.2", "RGB.NET.Core": "1.0.0-prerelease1", + "ReactiveUI": "17.1.9", "ReactiveUI.Validation": "2.2.1" } } diff --git a/src/Avalonia/Artemis.UI.Shared/Artemis.UI.Shared.csproj b/src/Avalonia/Artemis.UI.Shared/Artemis.UI.Shared.csproj index 8d084dfdd..1ceca6a48 100644 --- a/src/Avalonia/Artemis.UI.Shared/Artemis.UI.Shared.csproj +++ b/src/Avalonia/Artemis.UI.Shared/Artemis.UI.Shared.csproj @@ -24,6 +24,7 @@ + diff --git a/src/Avalonia/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs b/src/Avalonia/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs index 266f71062..e66117255 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs @@ -123,7 +123,7 @@ namespace Artemis.UI.Shared.Services.Builders public IControl Build() { return _action != null - ? new Button {Content = _text, Command = ReactiveCommand.Create(() => _action)} + ? new Button {Content = _text, Command = ReactiveCommand.Create(() => _action())} : new Button {Content = _text}; } } diff --git a/src/Avalonia/Artemis.UI.Shared/Styles/Border.axaml b/src/Avalonia/Artemis.UI.Shared/Styles/Border.axaml index b1d279fb3..27e3bf784 100644 --- a/src/Avalonia/Artemis.UI.Shared/Styles/Border.axaml +++ b/src/Avalonia/Artemis.UI.Shared/Styles/Border.axaml @@ -25,6 +25,10 @@ + + + + + + - + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs index 1d1ddb99e..1e2b76874 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs @@ -1,11 +1,14 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Reactive; using System.Reactive.Disposables; using Artemis.Core; using Artemis.UI.Ninject.Factories; using Artemis.UI.Services; using Artemis.UI.Shared; +using HistoricalReactiveCommand; using ReactiveUI; namespace Artemis.UI.Screens.ProfileEditor.ProfileTree @@ -13,15 +16,48 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree public class ProfileTreeViewModel : ActivatableViewModelBase { private readonly IProfileEditorVmFactory _profileEditorVmFactory; + private TreeItemViewModel? _selectedTreeItem; public ProfileTreeViewModel(IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory) { _profileEditorVmFactory = profileEditorVmFactory; - this.WhenActivated(d => profileEditorService.CurrentProfileConfiguration.WhereNotNull().Subscribe(Repopulate).DisposeWith(d)); + + this.WhenActivated(d => + { + ProfileConfiguration profileConfiguration = null!; + profileEditorService.CurrentProfileConfiguration.WhereNotNull().Subscribe(p => profileConfiguration = p).DisposeWith(d); + profileEditorService.CurrentProfileConfiguration.WhereNotNull().Subscribe(Repopulate).DisposeWith(d); + profileEditorService.CurrentProfileElement.WhereNotNull().Subscribe(SelectCurrentProfileElement).DisposeWith(d); + + Folder rootFolder = profileConfiguration.Profile!.GetRootFolder(); + AddLayer = ReactiveCommandEx.CreateWithHistory("AddLayerAtRoot", + // ReSharper disable once ObjectCreationAsStatement + () => new Layer(rootFolder, "New layer", 0), + () => rootFolder.RemoveChild(rootFolder.Children[0]), + profileConfiguration.Profile.EntityId.ToString() + ); + + AddFolder = ReactiveCommandEx.CreateWithHistory("AddFolderAtRoot", + // ReSharper disable once ObjectCreationAsStatement + () => new Folder(rootFolder, "New folder", 0), + () => rootFolder.RemoveChild(rootFolder.Children[0]), + profileConfiguration.Profile.EntityId.ToString() + ); + }); + this.WhenAnyValue(vm => vm.SelectedTreeItem).Subscribe(model => profileEditorService.ChangeCurrentProfileElement(model?.ProfileElement)); } + public ReactiveCommandWithHistory? AddLayer { get; set; } + public ReactiveCommandWithHistory? AddFolder { get; set; } + public ObservableCollection TreeItems { get; } = new(); + public TreeItemViewModel? SelectedTreeItem + { + get => _selectedTreeItem; + set => this.RaiseAndSetIfChanged(ref _selectedTreeItem, value); + } + private void Repopulate(ProfileConfiguration profileConfiguration) { if (TreeItems.Any()) @@ -33,11 +69,43 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree foreach (ProfileElement profileElement in profileConfiguration.Profile.GetRootFolder().Children) { if (profileElement is Folder folder) - TreeItems.Add(_profileEditorVmFactory.FolderTreeItemViewModel(folder)); + TreeItems.Add(_profileEditorVmFactory.FolderTreeItemViewModel(null, folder)); else if (profileElement is Layer layer) - TreeItems.Add(_profileEditorVmFactory.LayerTreeItemViewModel(layer)); + TreeItems.Add(_profileEditorVmFactory.LayerTreeItemViewModel(null, layer)); } - + } + + private void SelectCurrentProfileElement(RenderProfileElement element) + { + if (SelectedTreeItem?.ProfileElement == element) + return; + + // Find the tree item belonging to the selected element + List treeItems = GetAllTreeItems(TreeItems); + TreeItemViewModel? selected = treeItems.FirstOrDefault(e => e.ProfileElement == element); + + // Walk up the tree, expanding parents + TreeItemViewModel? currentParent = selected?.Parent; + while (currentParent != null) + { + currentParent.IsExpanded = true; + currentParent = currentParent.Parent; + } + + SelectedTreeItem = selected; + } + + private List GetAllTreeItems(ObservableCollection treeItems) + { + List result = new(); + foreach (TreeItemViewModel treeItemViewModel in treeItems) + { + result.Add(treeItemViewModel); + if (treeItemViewModel.Children.Any()) + result.AddRange(GetAllTreeItems(treeItemViewModel.Children)); + } + + return result; } } -} +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs index 28397234e..2497df852 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs @@ -1,8 +1,55 @@ -using Artemis.UI.Shared; +using System.Collections.ObjectModel; +using System.Linq; +using Artemis.Core; +using Artemis.UI.Shared; +using HistoricalReactiveCommand; +using ReactiveUI; namespace Artemis.UI.Screens.ProfileEditor.ProfileTree { public abstract class TreeItemViewModel : ActivatableViewModelBase { + private bool _isExpanded; + + protected TreeItemViewModel(TreeItemViewModel? parent, RenderProfileElement profileElement) + { + Parent = parent; + ProfileElement = profileElement; + + AddLayerAtIndex = ReactiveCommandEx.CreateWithHistory("AddLayerAtIndex", + (targetIndex, _) => new Layer(ProfileElement, "New folder", targetIndex), + (targetIndex, _) => + { + Layer toRemove = (Layer) ProfileElement.Children.ElementAt(targetIndex); + ProfileElement.RemoveChild(toRemove); + return toRemove; + }, + historyId: ProfileElement.Profile.EntityId.ToString() + ); + + AddFolderAtIndex = ReactiveCommandEx.CreateWithHistory("AddFolderAtIndex", + (targetIndex, _) => new Folder(ProfileElement, "New folder", targetIndex), + (targetIndex, _) => + { + Folder toRemove = (Folder) ProfileElement.Children.ElementAt(targetIndex); + ProfileElement.RemoveChild(toRemove); + return toRemove; + }, + historyId: ProfileElement.Profile.EntityId.ToString() + ); + } + + public ReactiveCommandWithHistory AddLayerAtIndex { get; set; } + public ReactiveCommandWithHistory AddFolderAtIndex { get; set; } + + public RenderProfileElement ProfileElement { get; } + public TreeItemViewModel? Parent { get; set; } + public ObservableCollection Children { get; } = new(); + + public bool IsExpanded + { + get => _isExpanded; + set => this.RaiseAndSetIfChanged(ref _isExpanded, value); + } } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorTitleBarView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorTitleBarView.axaml new file mode 100644 index 000000000..62afce2c8 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorTitleBarView.axaml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorTitleBarView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorTitleBarView.axaml.cs new file mode 100644 index 000000000..08b7a31c3 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorTitleBarView.axaml.cs @@ -0,0 +1,25 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; + +namespace Artemis.UI.Screens.ProfileEditor +{ + public partial class ProfileEditorTitleBarView : UserControl + { + public ProfileEditorTitleBarView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void MenuItem_OnSubmenuOpened(object? sender, RoutedEventArgs e) + { + + } + } +} diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorTitleBarViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorTitleBarViewModel.cs new file mode 100644 index 000000000..c30c92144 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorTitleBarViewModel.cs @@ -0,0 +1,24 @@ +using Artemis.UI.Screens.ProfileEditor.Panels.MenuBar; +using Artemis.UI.Services.Interfaces; +using Artemis.UI.Shared; + +namespace Artemis.UI.Screens.ProfileEditor +{ + public class ProfileEditorTitleBarViewModel : ViewModelBase + { + private readonly IDebugService _debugService; + + public ProfileEditorTitleBarViewModel(IDebugService debugService, MenuBarViewModel menuBarViewModel) + { + MenuBarViewModel = menuBarViewModel; + _debugService = debugService; + } + + public MenuBarViewModel MenuBarViewModel { get; } + + public void ShowDebugger() + { + _debugService.ShowDebugger(); + } + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml index a64869c3d..1d04329a8 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml @@ -24,204 +24,19 @@ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - @@ -239,7 +54,6 @@ - @@ -249,9 +63,9 @@ - + - + diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs index da3b0978b..63b13aa40 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Reactive.Disposables; using Artemis.Core; +using Artemis.UI.Screens.ProfileEditor.Panels.MenuBar; using Artemis.UI.Screens.ProfileEditor.ProfileTree; using Artemis.UI.Screens.ProfileEditor.VisualEditor; using Artemis.UI.Services; @@ -13,16 +14,28 @@ namespace Artemis.UI.Screens.ProfileEditor private ProfileConfiguration? _profile; /// - public ProfileEditorViewModel(IScreen hostScreen, IProfileEditorService profileEditorService, VisualEditorViewModel visualEditorViewModel, ProfileTreeViewModel profileTreeViewModel) + public ProfileEditorViewModel(IScreen hostScreen, + IProfileEditorService profileEditorService, + VisualEditorViewModel visualEditorViewModel, + ProfileTreeViewModel profileTreeViewModel, + ProfileEditorTitleBarViewModel profileEditorTitleBarViewModel, + MenuBarViewModel menuBarViewModel) : base(hostScreen, "profile-editor") { VisualEditorViewModel = visualEditorViewModel; ProfileTreeViewModel = profileTreeViewModel; - this.WhenActivated(disposables => { profileEditorService.CurrentProfileConfiguration.WhereNotNull().Subscribe(p => Profile = p).DisposeWith(disposables); }); + + if (OperatingSystem.IsWindows()) + TitleBarViewModel = profileEditorTitleBarViewModel; + else + MenuBarViewModel = menuBarViewModel; + + this.WhenActivated(disposables => profileEditorService.CurrentProfileConfiguration.WhereNotNull().Subscribe(p => Profile = p).DisposeWith(disposables)); } public VisualEditorViewModel VisualEditorViewModel { get; } public ProfileTreeViewModel ProfileTreeViewModel { get; } + public MenuBarViewModel? MenuBarViewModel { get; } public ProfileConfiguration? Profile { diff --git a/src/Avalonia/Artemis.UI/Screens/Root/DefaultTitleBarView.axaml b/src/Avalonia/Artemis.UI/Screens/Root/DefaultTitleBarView.axaml new file mode 100644 index 000000000..3ff09a332 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/Root/DefaultTitleBarView.axaml @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/Root/DefaultTitleBarView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/Root/DefaultTitleBarView.axaml.cs new file mode 100644 index 000000000..24d3e8070 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/Root/DefaultTitleBarView.axaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Artemis.UI.Screens.Root +{ + public partial class DefaultTitleBarView : UserControl + { + public DefaultTitleBarView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/Screens/Root/DefaultTitleBarViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Root/DefaultTitleBarViewModel.cs new file mode 100644 index 000000000..1f5060c7c --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/Root/DefaultTitleBarViewModel.cs @@ -0,0 +1,20 @@ +using Artemis.UI.Services.Interfaces; +using Artemis.UI.Shared; + +namespace Artemis.UI.Screens.Root +{ + public class DefaultTitleBarViewModel : ViewModelBase + { + private readonly IDebugService _debugService; + + public DefaultTitleBarViewModel(IDebugService debugService) + { + _debugService = debugService; + } + + public void ShowDebugger() + { + _debugService.ShowDebugger(); + } + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/Root/RootView.axaml b/src/Avalonia/Artemis.UI/Screens/Root/RootView.axaml index 6c5dde4ca..991a035dd 100644 --- a/src/Avalonia/Artemis.UI/Screens/Root/RootView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/Root/RootView.axaml @@ -11,7 +11,7 @@ - + diff --git a/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs index 1301bb454..52f22ac97 100644 --- a/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs @@ -1,4 +1,5 @@ -using System; + +using System; using System.Linq; using System.Threading.Tasks; using Artemis.Core; @@ -21,6 +22,7 @@ namespace Artemis.UI.Screens.Root public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvider { private readonly IAssetLoader _assetLoader; + private readonly DefaultTitleBarViewModel _defaultTitleBarViewModel; private readonly ICoreService _coreService; private readonly IDebugService _debugService; private readonly IClassicDesktopStyleApplicationLifetime _lifeTime; @@ -30,6 +32,7 @@ namespace Artemis.UI.Screens.Root private SidebarViewModel? _sidebarViewModel; private TrayIcon? _trayIcon; private TrayIcons? _trayIcons; + private ViewModelBase? _titleBarViewModel; public RootViewModel(ICoreService coreService, ISettingsService settingsService, @@ -38,6 +41,7 @@ namespace Artemis.UI.Screens.Root IMainWindowService mainWindowService, IDebugService debugService, IAssetLoader assetLoader, + DefaultTitleBarViewModel defaultTitleBarViewModel, ISidebarVmFactory sidebarVmFactory) { Router = new RoutingState(); @@ -47,6 +51,7 @@ namespace Artemis.UI.Screens.Root _windowService = windowService; _debugService = debugService; _assetLoader = assetLoader; + _defaultTitleBarViewModel = defaultTitleBarViewModel; _sidebarVmFactory = sidebarVmFactory; _lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current.ApplicationLifetime; @@ -54,15 +59,31 @@ namespace Artemis.UI.Screens.Root mainWindowService.ConfigureMainWindowProvider(this); DisplayAccordingToSettings(); + Router.CurrentViewModel.Subscribe(UpdateTitleBarViewModel); Task.Run(coreService.Initialize); } + private void UpdateTitleBarViewModel(IRoutableViewModel? viewModel) + { + if (viewModel is MainScreenViewModel mainScreenViewModel && mainScreenViewModel.TitleBarViewModel != null) + TitleBarViewModel = mainScreenViewModel.TitleBarViewModel; + else + TitleBarViewModel = _defaultTitleBarViewModel; + } + + public SidebarViewModel? SidebarViewModel { get => _sidebarViewModel; set => this.RaiseAndSetIfChanged(ref _sidebarViewModel, value); } + public ViewModelBase? TitleBarViewModel + { + get => _titleBarViewModel; + set => this.RaiseAndSetIfChanged(ref _titleBarViewModel, value); + } + private void CurrentMainWindowOnClosed(object? sender, EventArgs e) { _lifeTime.MainWindow = null; diff --git a/src/Avalonia/Artemis.UI/Services/ProfileEditorService.cs b/src/Avalonia/Artemis.UI/Services/ProfileEditorService.cs index 966bbc8ac..51b299614 100644 --- a/src/Avalonia/Artemis.UI/Services/ProfileEditorService.cs +++ b/src/Avalonia/Artemis.UI/Services/ProfileEditorService.cs @@ -22,8 +22,7 @@ namespace Artemis.UI.Services CurrentProfileConfiguration = Observable.Defer(() => Observable.Return(_currentProfileConfiguration)).Concat(_changeCurrentProfileConfiguration); CurrentProfileElement = Observable.Defer(() => Observable.Return(_currentProfileElement)).Concat(_changeCurrentProfileElement); } - - + public IObservable CurrentProfileConfiguration { get; } public IObservable CurrentProfileElement { get; } diff --git a/src/Avalonia/Artemis.UI/packages.lock.json b/src/Avalonia/Artemis.UI/packages.lock.json index 908975a90..36aa30742 100644 --- a/src/Avalonia/Artemis.UI/packages.lock.json +++ b/src/Avalonia/Artemis.UI/packages.lock.json @@ -115,6 +115,20 @@ "Material.Icons": "1.0.2" } }, + "ReactiveUI": { + "type": "Direct", + "requested": "[17.1.9, )", + "resolved": "17.1.9", + "contentHash": "bxH6uzEi1b6cfGoBBvCXWrdT18+Rleggi0R/vOaCYc+zvlSleJW6wlUtcWjrKzgQHD8EN3c02A4JBTt9oGSWWQ==", + "dependencies": { + "DynamicData": "7.4.3", + "Splat": "14.1.1", + "System.ComponentModel": "4.3.0", + "System.Diagnostics.Contracts": "4.3.0", + "System.Dynamic.Runtime": "4.3.0", + "System.Runtime.Serialization.Primitives": "4.3.0" + } + }, "ReactiveUI.Validation": { "type": "Direct", "requested": "[2.2.1, )", @@ -268,8 +282,8 @@ }, "DynamicData": { "type": "Transitive", - "resolved": "7.3.1", - "contentHash": "E9oTvWlAgzct0MuWt6k+0s+nSDA3LkFVvDwkMUTklIZZnva314KZAEF2vG4XX9I98ia+EpMqjte67jWEBJlsRw==", + "resolved": "7.4.3", + "contentHash": "7eGyREbtzyaRutMa+iToi2e41JboEVK9c1ZBcTvJOfEoTRIZX3hChIsxIvV0ErzMXtGHAIS2O0I8jLDUIds5wg==", "dependencies": { "System.Reactive": "5.0.0" } @@ -562,15 +576,6 @@ "Ninject": "3.3.3" } }, - "ReactiveUI": { - "type": "Transitive", - "resolved": "16.2.6", - "contentHash": "jf1RvD8HxHuA6CGQtheGHUCHzRrhpvo0z593Npsz7g8KJWXfGR45Dc9bILJHoymBxhdDD1L1WjUfh0fcucIPPg==", - "dependencies": { - "DynamicData": "7.3.1", - "Splat": "13.1.1" - } - }, "RGB.NET.Presets": { "type": "Transitive", "resolved": "1.0.0-prerelease1", @@ -928,6 +933,14 @@ "System.Text.Encoding": "4.3.0" } }, + "System.Diagnostics.Contracts": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "eelRRbnm+OloiQvp9CXS0ixjNQldjjkHO4iIkR5XH2VIP8sUB/SIpa1TdUW6/+HDcQ+MlhP3pNa1u5SbzYuWGA==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", @@ -1408,6 +1421,15 @@ "System.Runtime.Extensions": "4.3.0" } }, + "System.Runtime.Serialization.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", + "dependencies": { + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0" + } + }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", @@ -1763,6 +1785,7 @@ "FluentAvaloniaUI": "1.1.8", "Material.Icons.Avalonia": "1.0.2", "RGB.NET.Core": "1.0.0-prerelease1", + "ReactiveUI": "17.1.9", "ReactiveUI.Validation": "2.2.1" } }