1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Scripting - Add confirm dialog when discarding changes

Editor - Fix missing repeat mode button
Editor - Auto-save 1 sec after last change
General UI - Fix main window drag handle
This commit is contained in:
Robert 2022-07-16 10:42:20 +02:00
parent e3907e6a76
commit 311bdee8da
13 changed files with 103 additions and 25 deletions

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -64,6 +65,19 @@ internal class ProfileEditorService : IProfileEditorService
PixelsPerSecond = _pixelsPerSecondSubject.AsObservable(); PixelsPerSecond = _pixelsPerSecondSubject.AsObservable();
Tools = tools; Tools = tools;
SelectedKeyframes = selectedKeyframes; SelectedKeyframes = selectedKeyframes;
// Observe executing, undoing and redoing commands and run the auto-save after 1 second
History.Select(h => h?.Execute ?? Observable.Never<Unit>())
.Switch()
.Merge(History.Select(h => h?.Undo ?? Observable.Never<IProfileEditorCommand?>())
.Switch()
.Select(_ => Unit.Default))
.Merge(History.Select(h => h?.Redo ?? Observable.Never<IProfileEditorCommand?>())
.Switch()
.Select(_ => Unit.Default))
.Throttle(TimeSpan.FromSeconds(1))
.SelectMany(async _ => await AutoSaveProfileAsync())
.Subscribe();
} }
public IObservable<ProfileConfiguration?> ProfileConfiguration { get; } public IObservable<ProfileConfiguration?> ProfileConfiguration { get; }
@ -425,6 +439,22 @@ internal class ProfileEditorService : IProfileEditorService
return newHistory; return newHistory;
} }
private async Task<Unit> AutoSaveProfileAsync()
{
try
{
await SaveProfileAsync();
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Failed to auto-save profile", e);
_logger.Error(e, "Failed to auto-save profile");
throw;
}
return Unit.Default;
}
private void Tick(TimeSpan time) private void Tick(TimeSpan time)
{ {
if (_profileConfigurationSubject.Value?.Profile == null || _suspendedEditingSubject.Value) if (_profileConfigurationSubject.Value?.Profile == null || _suspendedEditingSubject.Value)

View File

@ -22,14 +22,13 @@ namespace Artemis.UI.Windows
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
{ {
ArtemisBootstrapper.Initialize(); if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
return;
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) ArtemisBootstrapper.Initialize();
{
_applicationStateManager = new ApplicationStateManager(_kernel!, desktop.Args); _applicationStateManager = new ApplicationStateManager(_kernel!, desktop.Args);
RegisterProviders(_kernel!); RegisterProviders(_kernel!);
} }
}
private void RegisterProviders(StandardKernel standardKernel) private void RegisterProviders(StandardKernel standardKernel)
{ {

View File

@ -8,6 +8,7 @@
Icon="/Assets/Images/Logo/application.ico" Icon="/Assets/Images/Logo/application.ico"
Title="Artemis 2.0"> Title="Artemis 2.0">
<Panel Name="RootPanel"> <Panel Name="RootPanel">
<Border Name="DragHandle" Background="Transparent" Height="40" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
<DockPanel> <DockPanel>
<ContentControl Name="SidebarContentControl" Content="{Binding SidebarViewModel}" DockPanel.Dock="Left" Width="240"> <ContentControl Name="SidebarContentControl" Content="{Binding SidebarViewModel}" DockPanel.Dock="Left" Width="240">
<ContentControl.Transitions> <ContentControl.Transitions>
@ -16,7 +17,7 @@
</Transitions> </Transitions>
</ContentControl.Transitions> </ContentControl.Transitions>
</ContentControl> </ContentControl>
<Border Background="Transparent" Name="TitleBar" DockPanel.Dock="Top"> <Border Name="TitleBar" DockPanel.Dock="Top">
<ContentControl Content="{Binding TitleBarViewModel}" /> <ContentControl Content="{Binding TitleBarViewModel}" />
</Border> </Border>
<ContentControl Content="{Binding}" /> <ContentControl Content="{Binding}" />

View File

@ -41,7 +41,7 @@ namespace Artemis.UI
if (coreAppTitleBar.TitleBar != null) if (coreAppTitleBar.TitleBar != null)
{ {
coreAppTitleBar.TitleBar.ExtendViewIntoTitleBar = true; coreAppTitleBar.TitleBar.ExtendViewIntoTitleBar = true;
SetTitleBar(this.Get<Border>("TitleBar")); SetTitleBar(this.Get<Border>("DragHandle"));
} }
} }

View File

@ -132,6 +132,7 @@ public class MenuBarViewModel : ActivatableViewModelBase
return; return;
await _windowService.ShowDialogAsync<ScriptsDialogViewModel, object?>(("profile", ProfileConfiguration.Profile)); await _windowService.ShowDialogAsync<ScriptsDialogViewModel, object?>(("profile", ProfileConfiguration.Profile));
await _profileEditorService.SaveProfileAsync();
} }
private async Task ExecuteAdaptProfile() private async Task ExecuteAdaptProfile()

View File

@ -34,7 +34,7 @@
<avalonia:MaterialIcon Kind="SkipNext" /> <avalonia:MaterialIcon Kind="SkipNext" />
</Button> </Button>
<ToggleButton Classes="icon-button icon-button-large" <ToggleButton Classes="icon-button icon-button-large"
IsChecked="{CompiledBinding Repeating}" IsChecked="{CompiledBinding Repeating, Mode=OneWay}"
Focusable="False" Focusable="False"
Command="{CompiledBinding CycleRepeating}"> Command="{CompiledBinding CycleRepeating}">
<ToolTip.Tip> <ToolTip.Tip>

View File

@ -28,6 +28,7 @@ public class PlaybackViewModel : ActivatableViewModelBase
{ {
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_settingsService = settingsService; _settingsService = settingsService;
_repeatTimeline = true;
this.WhenActivated(d => this.WhenActivated(d =>
{ {
@ -142,6 +143,7 @@ public class PlaybackViewModel : ActivatableViewModelBase
{ {
RepeatTimeline = false; RepeatTimeline = false;
RepeatSegment = true; RepeatSegment = true;
this.RaisePropertyChanged(nameof(Repeating));
} }
else if (RepeatSegment) else if (RepeatSegment)
{ {
@ -149,6 +151,7 @@ public class PlaybackViewModel : ActivatableViewModelBase
RepeatSegment = false; RepeatSegment = false;
Repeating = false; Repeating = false;
} }
} }
private TimeSpan GetCurrentSegmentStart() private TimeSpan GetCurrentSegmentStart()

View File

@ -6,6 +6,8 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
public class BrushConfigurationWindowView : ReactiveCoreWindow<BrushConfigurationWindowViewModel> public class BrushConfigurationWindowView : ReactiveCoreWindow<BrushConfigurationWindowViewModel>
{ {
private bool _canClose;
public BrushConfigurationWindowView() public BrushConfigurationWindowView()
{ {
InitializeComponent(); InitializeComponent();
@ -22,9 +24,14 @@ public class BrushConfigurationWindowView : ReactiveCoreWindow<BrushConfiguratio
private async void OnClosing(object? sender, CancelEventArgs e) private async void OnClosing(object? sender, CancelEventArgs e)
{ {
if (ViewModel == null) if (_canClose)
return; return;
e.Cancel = !await ViewModel.CanClose(); e.Cancel = true;
if (ViewModel == null || await ViewModel.CanClose())
{
_canClose = true;
Close();
}
} }
} }

View File

@ -6,6 +6,8 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows;
public class EffectConfigurationWindowView : ReactiveCoreWindow<EffectConfigurationWindowViewModel> public class EffectConfigurationWindowView : ReactiveCoreWindow<EffectConfigurationWindowViewModel>
{ {
private bool _canClose;
public EffectConfigurationWindowView() public EffectConfigurationWindowView()
{ {
InitializeComponent(); InitializeComponent();
@ -22,9 +24,14 @@ public class EffectConfigurationWindowView : ReactiveCoreWindow<EffectConfigurat
private async void OnClosing(object? sender, CancelEventArgs e) private async void OnClosing(object? sender, CancelEventArgs e)
{ {
if (ViewModel == null) if (_canClose)
return; return;
e.Cancel = !await ViewModel.CanClose(); e.Cancel = true;
if (ViewModel == null || await ViewModel.CanClose())
{
_canClose = true;
Close();
}
} }
} }

View File

@ -9,9 +9,11 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Scripting.ScriptsDialogView" x:Class="Artemis.UI.Screens.Scripting.ScriptsDialogView"
x:DataType="scripting:ScriptsDialogViewModel" x:DataType="scripting:ScriptsDialogViewModel"
Title="ScriptsDialogView"> Title="Artemis | Scripts"
Width="1200"
Height="750">
<DockPanel> <DockPanel>
<ScrollViewer DockPanel.Dock="Left" VerticalScrollBarVisibility="Auto" Width="240"> <ScrollViewer DockPanel.Dock="Left" VerticalScrollBarVisibility="Auto" Width="300" Margin="10">
<StackPanel> <StackPanel>
<ListBox Items="{CompiledBinding ScriptConfigurations}" SelectedItem="{CompiledBinding SelectedScript}"> <ListBox Items="{CompiledBinding ScriptConfigurations}" SelectedItem="{CompiledBinding SelectedScript}">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
@ -29,7 +31,8 @@
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{CompiledBinding ScriptConfiguration.Name}" Text="{CompiledBinding ScriptConfiguration.Name}"
IsVisible="{CompiledBinding !ScriptConfiguration.HasChanges}" /> IsVisible="{CompiledBinding !ScriptConfiguration.HasChanges}"
TextTrimming="CharacterEllipsis" />
<StackPanel Grid.Row="0" <StackPanel Grid.Row="0"
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -78,7 +81,7 @@
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
<Border DockPanel.Dock="Top" Classes="router-container"> <Border DockPanel.Dock="Top" Classes="router-container" Margin="0 10 0 0">
<ContentControl Content="{CompiledBinding ScriptEditorViewModel}" /> <ContentControl Content="{CompiledBinding ScriptEditorViewModel}" />
</Border> </Border>
</DockPanel> </DockPanel>

View File

@ -1,4 +1,5 @@
using Avalonia; using System.ComponentModel;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
@ -6,12 +7,28 @@ namespace Artemis.UI.Screens.Scripting;
public partial class ScriptsDialogView : ReactiveCoreWindow<ScriptsDialogViewModel> public partial class ScriptsDialogView : ReactiveCoreWindow<ScriptsDialogViewModel>
{ {
private bool _canClose;
public ScriptsDialogView() public ScriptsDialogView()
{ {
InitializeComponent(); InitializeComponent();
#if DEBUG #if DEBUG
this.AttachDevTools(); this.AttachDevTools();
#endif #endif
Closing += OnClosing;
}
private async void OnClosing(object? sender, CancelEventArgs e)
{
if (_canClose)
return;
e.Cancel = true;
if (ViewModel == null || await ViewModel.CanClose())
{
_canClose = true;
Close();
}
} }
private void InitializeComponent() private void InitializeComponent()

View File

@ -143,4 +143,18 @@ public class ScriptsDialogViewModel : DialogViewModelBase<object?>
// Select the new script // Select the new script
SelectedScript = ScriptConfigurations.LastOrDefault(); SelectedScript = ScriptConfigurations.LastOrDefault();
} }
public async Task<bool> CanClose()
{
if (!ScriptConfigurations.Any(s => s.ScriptConfiguration.HasChanges))
return true;
bool result = await _windowService.ShowConfirmContentDialog("Discard changes", "One or more scripts still have pending changes, do you want to discard them?");
if (!result)
return false;
foreach (ScriptConfigurationViewModel scriptConfigurationViewModel in ScriptConfigurations)
scriptConfigurationViewModel.ScriptConfiguration.DiscardPendingChanges();
return true;
}
} }

View File

@ -23,11 +23,7 @@ public class ViewLocator : IDataTemplate
throw new ArtemisUIException($"The views of activatable view models should inherit ReactiveUserControl<T>, in this case ReactiveUserControl<{data.GetType().Name}>."); throw new ArtemisUIException($"The views of activatable view models should inherit ReactiveUserControl<T>, in this case ReactiveUserControl<{data.GetType().Name}>.");
if (type != null) if (type != null)
{
Debug.WriteLine("[ViewLocator] Creating instance of '{0}'", type);
return (Control) Activator.CreateInstance(type)!; return (Control) Activator.CreateInstance(type)!;
}
return new TextBlock {Text = "Not Found: " + name}; return new TextBlock {Text = "Not Found: " + name};
} }