diff --git a/src/Artemis.UI/Extensions/ProfileElementExtensions.cs b/src/Artemis.UI/Extensions/ProfileElementExtensions.cs index 70ff9acfb..08ca23e2c 100644 --- a/src/Artemis.UI/Extensions/ProfileElementExtensions.cs +++ b/src/Artemis.UI/Extensions/ProfileElementExtensions.cs @@ -3,6 +3,7 @@ using System.Text; using System.Threading.Tasks; using Artemis.Core; using Artemis.Storage.Entities.Profile; +using Artemis.UI.Models; using Avalonia; using Avalonia.Input; @@ -21,7 +22,7 @@ public static class ProfileElementExtensions return; DataObject dataObject = new(); - string copy = CoreJson.SerializeObject(folder.FolderEntity, true); + string copy = CoreJson.SerializeObject(new FolderClipboardModel(folder), true); dataObject.Set(ClipboardDataFormat, copy); await Application.Current.Clipboard.SetDataObjectAsync(dataObject); } @@ -50,9 +51,8 @@ public static class ProfileElementExtensions object? entity = CoreJson.DeserializeObject(Encoding.Unicode.GetString(bytes), true); switch (entity) { - case FolderEntity folderEntity: - folderEntity.Id = Guid.NewGuid(); - return new Folder(parent.Profile, parent, folderEntity); + case FolderClipboardModel folderClipboardModel: + return folderClipboardModel.Paste(parent.Profile, parent); case LayerEntity layerEntity: layerEntity.Id = Guid.NewGuid(); return new Layer(parent.Profile, parent, layerEntity); diff --git a/src/Artemis.UI/MainWindow.axaml b/src/Artemis.UI/MainWindow.axaml index 1d8017354..7be2a5e6e 100644 --- a/src/Artemis.UI/MainWindow.axaml +++ b/src/Artemis.UI/MainWindow.axaml @@ -7,7 +7,8 @@ x:Class="Artemis.UI.MainWindow" Icon="/Assets/Images/Logo/application.ico" Title="Artemis 2.0" - WindowStartupLocation="CenterScreen"> + MinWidth="600" + MinHeight="400"> diff --git a/src/Artemis.UI/MainWindow.axaml.cs b/src/Artemis.UI/MainWindow.axaml.cs index 14732e730..336bfb46a 100644 --- a/src/Artemis.UI/MainWindow.axaml.cs +++ b/src/Artemis.UI/MainWindow.axaml.cs @@ -1,10 +1,15 @@ using System; +using System.Reactive; +using System.Reactive.Linq; +using Artemis.UI.Models; using Artemis.UI.Screens.Root; using Artemis.UI.Shared; using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.Threading; using FluentAvalonia.Core.ApplicationModel; +using ReactiveUI; namespace Artemis.UI; @@ -12,13 +17,17 @@ public class MainWindow : ReactiveCoreWindow { private readonly Panel _rootPanel; private readonly ContentControl _sidebarContentControl; + private bool _activated; public MainWindow() { Opened += OnOpened; Activated += OnActivated; Deactivated += OnDeactivated; + + ApplyWindowSize(); InitializeComponent(); + _rootPanel = this.Get("RootPanel"); _sidebarContentControl = this.Get("SidebarContentControl"); _rootPanel.LayoutUpdated += OnLayoutUpdated; @@ -26,6 +35,27 @@ public class MainWindow : ReactiveCoreWindow #if DEBUG this.AttachDevTools(); #endif + + Observable.FromEventPattern(x => PositionChanged += x, x => PositionChanged -= x) + .Select(_ => Unit.Default) + .Merge(this.WhenAnyValue(vm => vm.WindowState, vm => vm.Width, vm => vm.Width, vm => vm.Height).Select(_ => Unit.Default)) + .Throttle(TimeSpan.FromMilliseconds(200), AvaloniaScheduler.Instance) + .Subscribe(_ => SaveWindowSize()); + } + + private void ApplyWindowSize() + { + _activated = true; + RootViewModel.WindowSizeSetting?.Value?.ApplyToWindow(this); + } + + private void SaveWindowSize() + { + if (RootViewModel.WindowSizeSetting == null || !_activated) + return; + + RootViewModel.WindowSizeSetting.Value ??= new WindowSize(); + RootViewModel.WindowSizeSetting.Value.ApplyFromWindow(this); } // TODO: Replace with a media query once https://github.com/AvaloniaUI/Avalonia/pull/7938 is implemented diff --git a/src/Artemis.UI/Models/FolderClipboardModel.cs b/src/Artemis.UI/Models/FolderClipboardModel.cs new file mode 100644 index 000000000..98301d394 --- /dev/null +++ b/src/Artemis.UI/Models/FolderClipboardModel.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using Artemis.Core; +using Artemis.Storage.Entities.Profile; +using Artemis.Storage.Entities.Profile.Abstract; +using Artemis.UI.Exceptions; + +namespace Artemis.UI.Models; + +public class FolderClipboardModel +{ + public FolderClipboardModel(Folder folder) + { + FolderEntity = folder.FolderEntity; + Folders = new List(); + Layers = new List(); + foreach (Folder allFolder in folder.GetAllFolders()) + Folders.Add(allFolder.FolderEntity); + foreach (Layer allLayer in folder.GetAllLayers()) + Layers.Add(allLayer.LayerEntity); + } + + // ReSharper disable once UnusedMember.Global - For JSON.NET + public FolderClipboardModel() + { + FolderEntity = null; + Folders = new List(); + Layers = new List(); + } + + public FolderEntity? FolderEntity { get; set; } + public List Folders { get; set; } + public List Layers { get; set; } + public bool HasBeenPasted { get; set; } + + public Folder Paste(Profile profile, ProfileElement parent) + { + if (FolderEntity == null) + throw new ArtemisUIException("Couldn't paste folder because FolderEntity deserialized as null"); + if (HasBeenPasted) + throw new ArtemisUIException("Clipboard model can only be pasted once"); + + HasBeenPasted = true; + + // Generate new GUIDs + ReplaceGuid(FolderEntity); + foreach (FolderEntity folderEntity in Folders) + ReplaceGuid(folderEntity); + foreach (LayerEntity layerEntity in Layers) + ReplaceGuid(layerEntity); + + // Inject the pasted elements into the profile + profile.ProfileEntity.Folders.AddRange(Folders); + profile.ProfileEntity.Layers.AddRange(Layers); + + // Let the folder initialize and load as usual + FolderEntity.Name += " - copy"; + Folder folder = new(profile, parent, FolderEntity); + return folder; + } + + private void ReplaceGuid(RenderElementEntity parent) + { + Guid old = parent.Id; + parent.Id = Guid.NewGuid(); + + foreach (FolderEntity child in Folders) + { + if (child.ParentId == old) + child.ParentId = parent.Id; + } + + foreach (LayerEntity child in Layers) + { + if (child.ParentId == old) + child.ParentId = parent.Id; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Models/WindowSize.cs b/src/Artemis.UI/Models/WindowSize.cs new file mode 100644 index 000000000..d6872d78c --- /dev/null +++ b/src/Artemis.UI/Models/WindowSize.cs @@ -0,0 +1,71 @@ +using System; +using Avalonia; +using Avalonia.Controls; + +namespace Artemis.UI.Models; + +public class WindowSize +{ + private bool _applying; + public int Top { get; set; } + public int Left { get; set; } + public double Width { get; set; } + public double Height { get; set; } + public int MaximizedTop { get; set; } + public int MaximizedLeft { get; set; } + public double MaximizedWidth { get; set; } + public double MaximizedHeight { get; set; } + public bool IsMaximized { get; set; } + + public void ApplyFromWindow(Window window) + { + if (_applying) + return; + + if (double.IsNaN(window.Width) || double.IsNaN(window.Height)) + return; + + IsMaximized = window.WindowState == WindowState.Maximized; + if (IsMaximized) + { + MaximizedTop = window.Position.Y; + MaximizedLeft = window.Position.X; + MaximizedHeight = window.Height; + MaximizedWidth = window.Width; + } + else + { + Top = window.Position.Y; + Left = window.Position.X; + Height = window.Height; + Width = window.Width; + } + } + + public void ApplyToWindow(Window window) + { + if (_applying) + return; + + try + { + _applying = true; + if (IsMaximized) + { + window.Position = new PixelPoint(MaximizedLeft, MaximizedTop); + window.WindowState = WindowState.Maximized; + } + else + { + window.Position = new PixelPoint(Left, Top); + window.Height = Height; + window.Width = Width; + window.WindowState = WindowState.Normal; + } + } + finally + { + _applying = false; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Artemis.UI/Screens/Root/RootViewModel.cs index f34d77f50..10481f25c 100644 --- a/src/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Artemis.UI/Screens/Root/RootViewModel.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; +using Artemis.UI.Models; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.Sidebar; using Artemis.UI.Services.Interfaces; @@ -44,7 +45,8 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi ISidebarVmFactory sidebarVmFactory) { Router = new RoutingState(); - + WindowSizeSetting = settingsService.GetSetting("WindowSize"); + _coreService = coreService; _settingsService = settingsService; _windowService = windowService; @@ -54,7 +56,7 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi _defaultTitleBarViewModel = defaultTitleBarViewModel; _sidebarVmFactory = sidebarVmFactory; _lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current!.ApplicationLifetime!; - + mainWindowService.ConfigureMainWindowProvider(this); DisplayAccordingToSettings(); @@ -90,6 +92,7 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi private void CurrentMainWindowOnClosing(object? sender, EventArgs e) { + WindowSizeSetting.Save(); _lifeTime.MainWindow = null; SidebarViewModel = null; Router.NavigateAndReset.Execute(new EmptyViewModel(this, "blank")).Subscribe(); @@ -121,6 +124,8 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi /// public RoutingState Router { get; } + public static PluginSetting? WindowSizeSetting { get; private set; } + #region Tray commands public void OpenScreen(string displayName) @@ -170,7 +175,6 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi _lifeTime.MainWindow.Closing += CurrentMainWindowOnClosing; } - _lifeTime.MainWindow.WindowState = WindowState.Normal; _lifeTime.MainWindow.Activate(); OnMainWindowOpened(); } @@ -226,6 +230,11 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi } #endregion + + public void SaveWindowBounds(int x, int y, int width, int height) + { + throw new NotImplementedException(); + } } internal class EmptyViewModel : MainScreenViewModel diff --git a/src/Artemis.VisualScripting/Converters/NumericConverter.cs b/src/Artemis.VisualScripting/Converters/NumericConverter.cs index 320250b7e..397b0564c 100644 --- a/src/Artemis.VisualScripting/Converters/NumericConverter.cs +++ b/src/Artemis.VisualScripting/Converters/NumericConverter.cs @@ -15,7 +15,8 @@ public class NumericConverter : IValueConverter if (value is not Numeric numeric) return value; - return Numeric.IsTypeCompatible(targetType) ? numeric.ToType(targetType, NumberFormatInfo.InvariantInfo) : value; + object result = Numeric.IsTypeCompatible(targetType) ? numeric.ToType(targetType, NumberFormatInfo.InvariantInfo) : value; + return result; } /// diff --git a/src/Artemis.VisualScripting/Nodes/Static/Screens/StaticNumericValueNodeCustomView.axaml b/src/Artemis.VisualScripting/Nodes/Static/Screens/StaticNumericValueNodeCustomView.axaml index 46395bfde..e420ebbc4 100644 --- a/src/Artemis.VisualScripting/Nodes/Static/Screens/StaticNumericValueNodeCustomView.axaml +++ b/src/Artemis.VisualScripting/Nodes/Static/Screens/StaticNumericValueNodeCustomView.axaml @@ -12,9 +12,11 @@ - - - - + \ No newline at end of file