mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Profile editor - Copy child elements when copying folders
UI - Save window position/size Numberic node - Correctly show 0 instead of an empty input when reloading after a save
This commit is contained in:
parent
f733ce02de
commit
65a2aa70e2
@ -3,6 +3,7 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
using Artemis.UI.Models;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ public static class ProfileElementExtensions
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
DataObject dataObject = new();
|
DataObject dataObject = new();
|
||||||
string copy = CoreJson.SerializeObject(folder.FolderEntity, true);
|
string copy = CoreJson.SerializeObject(new FolderClipboardModel(folder), true);
|
||||||
dataObject.Set(ClipboardDataFormat, copy);
|
dataObject.Set(ClipboardDataFormat, copy);
|
||||||
await Application.Current.Clipboard.SetDataObjectAsync(dataObject);
|
await Application.Current.Clipboard.SetDataObjectAsync(dataObject);
|
||||||
}
|
}
|
||||||
@ -50,9 +51,8 @@ public static class ProfileElementExtensions
|
|||||||
object? entity = CoreJson.DeserializeObject(Encoding.Unicode.GetString(bytes), true);
|
object? entity = CoreJson.DeserializeObject(Encoding.Unicode.GetString(bytes), true);
|
||||||
switch (entity)
|
switch (entity)
|
||||||
{
|
{
|
||||||
case FolderEntity folderEntity:
|
case FolderClipboardModel folderClipboardModel:
|
||||||
folderEntity.Id = Guid.NewGuid();
|
return folderClipboardModel.Paste(parent.Profile, parent);
|
||||||
return new Folder(parent.Profile, parent, folderEntity);
|
|
||||||
case LayerEntity layerEntity:
|
case LayerEntity layerEntity:
|
||||||
layerEntity.Id = Guid.NewGuid();
|
layerEntity.Id = Guid.NewGuid();
|
||||||
return new Layer(parent.Profile, parent, layerEntity);
|
return new Layer(parent.Profile, parent, layerEntity);
|
||||||
|
|||||||
@ -7,7 +7,8 @@
|
|||||||
x:Class="Artemis.UI.MainWindow"
|
x:Class="Artemis.UI.MainWindow"
|
||||||
Icon="/Assets/Images/Logo/application.ico"
|
Icon="/Assets/Images/Logo/application.ico"
|
||||||
Title="Artemis 2.0"
|
Title="Artemis 2.0"
|
||||||
WindowStartupLocation="CenterScreen">
|
MinWidth="600"
|
||||||
|
MinHeight="400">
|
||||||
<Panel Name="RootPanel">
|
<Panel Name="RootPanel">
|
||||||
<Border Name="DragHandle" Background="Transparent" Height="40" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
|
<Border Name="DragHandle" Background="Transparent" Height="40" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
|
|||||||
@ -1,10 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Reactive;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using Artemis.UI.Models;
|
||||||
using Artemis.UI.Screens.Root;
|
using Artemis.UI.Screens.Root;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.Threading;
|
||||||
using FluentAvalonia.Core.ApplicationModel;
|
using FluentAvalonia.Core.ApplicationModel;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI;
|
namespace Artemis.UI;
|
||||||
|
|
||||||
@ -12,13 +17,17 @@ public class MainWindow : ReactiveCoreWindow<RootViewModel>
|
|||||||
{
|
{
|
||||||
private readonly Panel _rootPanel;
|
private readonly Panel _rootPanel;
|
||||||
private readonly ContentControl _sidebarContentControl;
|
private readonly ContentControl _sidebarContentControl;
|
||||||
|
private bool _activated;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
Opened += OnOpened;
|
Opened += OnOpened;
|
||||||
Activated += OnActivated;
|
Activated += OnActivated;
|
||||||
Deactivated += OnDeactivated;
|
Deactivated += OnDeactivated;
|
||||||
|
|
||||||
|
ApplyWindowSize();
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
_rootPanel = this.Get<Panel>("RootPanel");
|
_rootPanel = this.Get<Panel>("RootPanel");
|
||||||
_sidebarContentControl = this.Get<ContentControl>("SidebarContentControl");
|
_sidebarContentControl = this.Get<ContentControl>("SidebarContentControl");
|
||||||
_rootPanel.LayoutUpdated += OnLayoutUpdated;
|
_rootPanel.LayoutUpdated += OnLayoutUpdated;
|
||||||
@ -26,6 +35,27 @@ public class MainWindow : ReactiveCoreWindow<RootViewModel>
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
this.AttachDevTools();
|
this.AttachDevTools();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Observable.FromEventPattern<PixelPointEventArgs>(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
|
// TODO: Replace with a media query once https://github.com/AvaloniaUI/Avalonia/pull/7938 is implemented
|
||||||
|
|||||||
79
src/Artemis.UI/Models/FolderClipboardModel.cs
Normal file
79
src/Artemis.UI/Models/FolderClipboardModel.cs
Normal file
@ -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<FolderEntity>();
|
||||||
|
Layers = new List<LayerEntity>();
|
||||||
|
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<FolderEntity>();
|
||||||
|
Layers = new List<LayerEntity>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FolderEntity? FolderEntity { get; set; }
|
||||||
|
public List<FolderEntity> Folders { get; set; }
|
||||||
|
public List<LayerEntity> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/Artemis.UI/Models/WindowSize.cs
Normal file
71
src/Artemis.UI/Models/WindowSize.cs
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Models;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.Sidebar;
|
using Artemis.UI.Screens.Sidebar;
|
||||||
using Artemis.UI.Services.Interfaces;
|
using Artemis.UI.Services.Interfaces;
|
||||||
@ -44,7 +45,8 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi
|
|||||||
ISidebarVmFactory sidebarVmFactory)
|
ISidebarVmFactory sidebarVmFactory)
|
||||||
{
|
{
|
||||||
Router = new RoutingState();
|
Router = new RoutingState();
|
||||||
|
WindowSizeSetting = settingsService.GetSetting<WindowSize?>("WindowSize");
|
||||||
|
|
||||||
_coreService = coreService;
|
_coreService = coreService;
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
@ -54,7 +56,7 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi
|
|||||||
_defaultTitleBarViewModel = defaultTitleBarViewModel;
|
_defaultTitleBarViewModel = defaultTitleBarViewModel;
|
||||||
_sidebarVmFactory = sidebarVmFactory;
|
_sidebarVmFactory = sidebarVmFactory;
|
||||||
_lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current!.ApplicationLifetime!;
|
_lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current!.ApplicationLifetime!;
|
||||||
|
|
||||||
mainWindowService.ConfigureMainWindowProvider(this);
|
mainWindowService.ConfigureMainWindowProvider(this);
|
||||||
|
|
||||||
DisplayAccordingToSettings();
|
DisplayAccordingToSettings();
|
||||||
@ -90,6 +92,7 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi
|
|||||||
|
|
||||||
private void CurrentMainWindowOnClosing(object? sender, EventArgs e)
|
private void CurrentMainWindowOnClosing(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
WindowSizeSetting.Save();
|
||||||
_lifeTime.MainWindow = null;
|
_lifeTime.MainWindow = null;
|
||||||
SidebarViewModel = null;
|
SidebarViewModel = null;
|
||||||
Router.NavigateAndReset.Execute(new EmptyViewModel(this, "blank")).Subscribe();
|
Router.NavigateAndReset.Execute(new EmptyViewModel(this, "blank")).Subscribe();
|
||||||
@ -121,6 +124,8 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public RoutingState Router { get; }
|
public RoutingState Router { get; }
|
||||||
|
|
||||||
|
public static PluginSetting<WindowSize?>? WindowSizeSetting { get; private set; }
|
||||||
|
|
||||||
#region Tray commands
|
#region Tray commands
|
||||||
|
|
||||||
public void OpenScreen(string displayName)
|
public void OpenScreen(string displayName)
|
||||||
@ -170,7 +175,6 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi
|
|||||||
_lifeTime.MainWindow.Closing += CurrentMainWindowOnClosing;
|
_lifeTime.MainWindow.Closing += CurrentMainWindowOnClosing;
|
||||||
}
|
}
|
||||||
|
|
||||||
_lifeTime.MainWindow.WindowState = WindowState.Normal;
|
|
||||||
_lifeTime.MainWindow.Activate();
|
_lifeTime.MainWindow.Activate();
|
||||||
OnMainWindowOpened();
|
OnMainWindowOpened();
|
||||||
}
|
}
|
||||||
@ -226,6 +230,11 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
public void SaveWindowBounds(int x, int y, int width, int height)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class EmptyViewModel : MainScreenViewModel
|
internal class EmptyViewModel : MainScreenViewModel
|
||||||
|
|||||||
@ -15,7 +15,8 @@ public class NumericConverter : IValueConverter
|
|||||||
if (value is not Numeric numeric)
|
if (value is not Numeric numeric)
|
||||||
return value;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@ -12,9 +12,11 @@
|
|||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<converters:NumericConverter x:Key="NumericConverter" />
|
<converters:NumericConverter x:Key="NumericConverter" />
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<controls:NumberBox VerticalAlignment="Center" MinWidth="75" SimpleNumberFormat="F3" Classes="condensed">
|
<controls:NumberBox VerticalAlignment="Center"
|
||||||
<Interaction.Behaviors>
|
MinWidth="75"
|
||||||
<behaviors:LostFocusNumberBoxBindingBehavior Value="{CompiledBinding CurrentValue, Converter={StaticResource NumericConverter}}"/>
|
SimpleNumberFormat="F3"
|
||||||
</Interaction.Behaviors>
|
Classes="condensed"
|
||||||
|
AcceptsExpression="True"
|
||||||
|
Value="{CompiledBinding CurrentValue, Converter={StaticResource NumericConverter}}">
|
||||||
</controls:NumberBox>
|
</controls:NumberBox>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
Loading…
x
Reference in New Issue
Block a user