diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index a1f8400e8..81ec38993 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -14,6 +14,8 @@ namespace Artemis.Core /// public sealed class Folder : RenderProfileElement { + private bool _isExpanded; + /// /// Creates a new instance of the class and adds itself to the child collection of the provided /// @@ -46,6 +48,7 @@ namespace Artemis.Core Profile = profile; Parent = parent; Name = folderEntity.Name; + IsExpanded = folderEntity.IsExpanded; Suspended = folderEntity.Suspended; Order = folderEntity.Order; @@ -57,6 +60,15 @@ namespace Artemis.Core /// public bool IsRootFolder => Parent == Profile; + /// + /// Gets or sets a boolean indicating whether this folder is expanded + /// + public bool IsExpanded + { + get => _isExpanded; + set => SetAndNotify(ref _isExpanded, value); + } + /// /// Gets the folder entity this folder uses for persistent storage /// @@ -72,8 +84,10 @@ namespace Artemis.Core { List result = new(); foreach (BaseLayerEffect layerEffect in LayerEffects) + { if (layerEffect.BaseProperties != null) result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties()); + } return result; } @@ -151,27 +165,6 @@ namespace Artemis.Core return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; } - internal void CalculateRenderProperties() - { - if (Disposed) - throw new ObjectDisposedException("Folder"); - - SKPath path = new() {FillType = SKPathFillType.Winding}; - foreach (ProfileElement child in Children) - { - if (child is RenderProfileElement effectChild && effectChild.Path != null) - path.AddPath(effectChild.Path); - } - - Path = path; - - // Folder render properties are based on child paths and thus require an update - if (Parent is Folder folder) - folder.CalculateRenderProperties(); - - OnRenderPropertiesUpdated(); - } - #region Rendering /// @@ -209,7 +202,7 @@ namespace Artemis.Core canvas.SaveLayer(layerPaint); canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y); - + // Iterate the children in reverse because the first layer must be rendered last to end up on top for (int index = Children.Count - 1; index > -1; index--) Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top)); @@ -229,57 +222,6 @@ namespace Artemis.Core #endregion - /// - protected override void Dispose(bool disposing) - { - Disposed = true; - - Disable(); - foreach (ProfileElement profileElement in Children) - profileElement.Dispose(); - - base.Dispose(disposing); - } - - internal override void Load() - { - ExpandedPropertyGroups.AddRange(FolderEntity.ExpandedPropertyGroups); - Reset(); - - // Load child folders - foreach (FolderEntity childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId)) - ChildrenList.Add(new Folder(Profile, this, childFolder)); - // Load child layers - foreach (LayerEntity childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId)) - ChildrenList.Add(new Layer(Profile, this, childLayer)); - - // Ensure order integrity, should be unnecessary but no one is perfect specially me - ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList(); - for (int index = 0; index < ChildrenList.Count; index++) - ChildrenList[index].Order = index + 1; - - LoadRenderElement(); - } - - internal override void Save() - { - if (Disposed) - throw new ObjectDisposedException("Folder"); - - FolderEntity.Id = EntityId; - FolderEntity.ParentId = Parent?.EntityId ?? new Guid(); - - FolderEntity.Order = Order; - FolderEntity.Name = Name; - FolderEntity.Suspended = Suspended; - - FolderEntity.ProfileId = Profile.EntityId; - FolderEntity.ExpandedPropertyGroups.Clear(); - FolderEntity.ExpandedPropertyGroups.AddRange(ExpandedPropertyGroups); - - SaveRenderElement(); - } - /// public override void Enable() { @@ -320,18 +262,87 @@ namespace Artemis.Core Enabled = false; } - #region Events - /// /// Occurs when a property affecting the rendering properties of this folder has been updated /// public event EventHandler? RenderPropertiesUpdated; + /// + protected override void Dispose(bool disposing) + { + Disposed = true; + + Disable(); + foreach (ProfileElement profileElement in Children) + profileElement.Dispose(); + + base.Dispose(disposing); + } + + internal void CalculateRenderProperties() + { + if (Disposed) + throw new ObjectDisposedException("Folder"); + + SKPath path = new() {FillType = SKPathFillType.Winding}; + foreach (ProfileElement child in Children) + { + if (child is RenderProfileElement effectChild && effectChild.Path != null) + path.AddPath(effectChild.Path); + } + + Path = path; + + // Folder render properties are based on child paths and thus require an update + if (Parent is Folder folder) + folder.CalculateRenderProperties(); + + OnRenderPropertiesUpdated(); + } + + internal override void Load() + { + ExpandedPropertyGroups.AddRange(FolderEntity.ExpandedPropertyGroups); + Reset(); + + // Load child folders + foreach (FolderEntity childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId)) + ChildrenList.Add(new Folder(Profile, this, childFolder)); + // Load child layers + foreach (LayerEntity childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId)) + ChildrenList.Add(new Layer(Profile, this, childLayer)); + + // Ensure order integrity, should be unnecessary but no one is perfect specially me + ChildrenList = ChildrenList.OrderBy(c => c.Order).ToList(); + for (int index = 0; index < ChildrenList.Count; index++) + ChildrenList[index].Order = index + 1; + + LoadRenderElement(); + } + + internal override void Save() + { + if (Disposed) + throw new ObjectDisposedException("Folder"); + + FolderEntity.Id = EntityId; + FolderEntity.ParentId = Parent?.EntityId ?? new Guid(); + + FolderEntity.Order = Order; + FolderEntity.Name = Name; + FolderEntity.IsExpanded = IsExpanded; + FolderEntity.Suspended = Suspended; + + FolderEntity.ProfileId = Profile.EntityId; + FolderEntity.ExpandedPropertyGroups.Clear(); + FolderEntity.ExpandedPropertyGroups.AddRange(ExpandedPropertyGroups); + + SaveRenderElement(); + } + private void OnRenderPropertiesUpdated() { RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty); } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index 75ec5d66b..e2870dec1 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -14,6 +14,7 @@ namespace Artemis.Core { private readonly object _lock = new(); private bool _isFreshImport; + private ProfileElement? _lastSelectedProfileElement; internal Profile(ProfileConfiguration configuration, ProfileEntity profileEntity) : base(null!) { @@ -60,6 +61,15 @@ namespace Artemis.Core set => SetAndNotify(ref _isFreshImport, value); } + /// + /// Gets or sets the last selected profile element of this profile + /// + public ProfileElement? LastSelectedProfileElement + { + get => _lastSelectedProfileElement; + set => SetAndNotify(ref _lastSelectedProfileElement, value); + } + /// /// Gets the profile entity this profile uses for persistent storage /// @@ -187,6 +197,15 @@ namespace Artemis.Core } } + if (ProfileEntity.LastSelectedProfileElement != Guid.Empty) + { + LastSelectedProfileElement = GetAllFolders().FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement); + if (LastSelectedProfileElement == null) + LastSelectedProfileElement = GetAllLayers().FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement); + } + else + LastSelectedProfileElement = null; + foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations) scriptConfiguration.Script?.Dispose(); ScriptConfigurations.Clear(); @@ -201,6 +220,7 @@ namespace Artemis.Core ProfileEntity.Id = EntityId; ProfileEntity.Name = Configuration.Name; ProfileEntity.IsFreshImport = IsFreshImport; + ProfileEntity.LastSelectedProfileElement = LastSelectedProfileElement?.EntityId ?? Guid.Empty; foreach (ProfileElement profileElement in Children) profileElement.Save(); diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index f8d8a6d47..bd4b4994a 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -397,7 +397,7 @@ namespace Artemis.Core if (conditionMet && !DisplayConditionMet && Timeline.IsFinished) Timeline.JumpToStart(); // If regular conditions are no longer met, jump to the end segment if stop mode requires it - if (!conditionMet && DisplayConditionMet && Timeline.StopMode == TimelineStopMode.SkipToEnd) + if (!conditionMet && Timeline.StopMode == TimelineStopMode.SkipToEnd) Timeline.JumpToEndSegment(); } else if (conditionMet) diff --git a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs index f4103388e..21e1a3157 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs @@ -179,7 +179,8 @@ namespace Artemis.Core { if (_disposed) throw new ObjectDisposedException("ProfileConfiguration"); - + if (IsBeingEdited) + return true; if (Category.IsSuspended || IsSuspended || IsMissingModule) return false; diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index f026abccb..5cdf00f06 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -209,14 +209,15 @@ namespace Artemis.Core.Services ProcessPendingKeyEvents(profileConfiguration); // Profiles being edited are updated at their own leisure - if (profileConfiguration.IsBeingEdited) + if (profileConfiguration.IsBeingEdited && RenderForEditor) continue; bool shouldBeActive = profileConfiguration.ShouldBeActive(false); if (shouldBeActive) { profileConfiguration.Update(); - shouldBeActive = profileConfiguration.ActivationConditionMet; + if (!profileConfiguration.IsBeingEdited) + shouldBeActive = profileConfiguration.ActivationConditionMet; } try @@ -245,6 +246,16 @@ namespace Artemis.Core.Services { lock (_profileCategories) { + ProfileConfiguration? editedProfileConfiguration = _profileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(p => p.IsBeingEdited); + if (editedProfileConfiguration != null) + { + editedProfileConfiguration.Profile?.Render(canvas, SKPointI.Empty); + return; + } + + if (RenderForEditor) + return; + // Iterate the children in reverse because the first category must be rendered last to end up on top for (int i = _profileCategories.Count - 1; i > -1; i--) { @@ -254,17 +265,9 @@ namespace Artemis.Core.Services try { ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j]; - if (RenderForEditor) - { - if (profileConfiguration.IsBeingEdited) - profileConfiguration.Profile?.Render(canvas, SKPointI.Empty); - } - else - { - // Ensure all criteria are met before rendering - if (!profileConfiguration.IsSuspended && !profileConfiguration.IsMissingModule && profileConfiguration.ActivationConditionMet) - profileConfiguration.Profile?.Render(canvas, SKPointI.Empty); - } + // Ensure all criteria are met before rendering + if (!profileConfiguration.IsSuspended && !profileConfiguration.IsMissingModule && profileConfiguration.ActivationConditionMet) + profileConfiguration.Profile?.Render(canvas, SKPointI.Empty); } catch (Exception e) { @@ -396,13 +399,6 @@ namespace Artemis.Core.Services } } - /// - /// Creates a new profile configuration and adds it to the provided - /// - /// The profile category to add the profile to - /// The name of the new profile configuration - /// The icon of the new profile configuration - /// The newly created profile configuration public ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon) { ProfileConfiguration configuration = new(category, name, icon); @@ -414,10 +410,6 @@ namespace Artemis.Core.Services return configuration; } - /// - /// Removes the provided profile configuration from the - /// - /// public void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration) { profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration); diff --git a/src/Artemis.Storage/Entities/Profile/FolderEntity.cs b/src/Artemis.Storage/Entities/Profile/FolderEntity.cs index 035210069..0f6cdde1b 100644 --- a/src/Artemis.Storage/Entities/Profile/FolderEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/FolderEntity.cs @@ -16,6 +16,7 @@ namespace Artemis.Storage.Entities.Profile public int Order { get; set; } public string Name { get; set; } + public bool IsExpanded { get; set; } public bool Suspended { get; set; } [BsonRef("ProfileEntity")] diff --git a/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs index 0a65c180f..abc390d98 100644 --- a/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs @@ -18,6 +18,7 @@ namespace Artemis.Storage.Entities.Profile public string Name { get; set; } public bool IsFreshImport { get; set; } + public Guid LastSelectedProfileElement { get; set; } public List Folders { get; set; } public List Layers { get; set; } diff --git a/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs b/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs index cbc59f4f8..499afb5d7 100644 --- a/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs +++ b/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs @@ -27,12 +27,12 @@ namespace Artemis.UI.Shared /// /// Gets or sets the minimum value /// - public static readonly DependencyProperty MinProperty = DependencyProperty.Register(nameof(Min), typeof(float?), typeof(DraggableFloat)); + public static readonly DependencyProperty MinProperty = DependencyProperty.Register(nameof(Min), typeof(object), typeof(DraggableFloat)); /// /// Gets or sets the maximum value /// - public static readonly DependencyProperty MaxProperty = DependencyProperty.Register(nameof(Max), typeof(float?), typeof(DraggableFloat)); + public static readonly DependencyProperty MaxProperty = DependencyProperty.Register(nameof(Max), typeof(object), typeof(DraggableFloat)); /// /// Occurs when the value has changed @@ -90,18 +90,18 @@ namespace Artemis.UI.Shared /// /// Gets or sets the minimum value /// - public float? Min + public object? Min { - get => (float?) GetValue(MinProperty); + get => (object?) GetValue(MinProperty); set => SetValue(MinProperty, value); } /// /// Gets or sets the maximum value /// - public float? Max + public object? Max { - get => (float?) GetValue(MaxProperty); + get => (object?) GetValue(MaxProperty); set => SetValue(MaxProperty, value); } @@ -207,10 +207,11 @@ namespace Artemis.UI.Shared stepSize = stepSize * 10; float value = (float) RoundToNearestOf(startValue + stepSize * (x - startX), stepSize); - if (Min != null) - value = Math.Max(value, Min.Value); - if (Max != null) - value = Math.Min(value, Max.Value); + + if (Min != null && float.TryParse(Min.ToString(), out float minFloat)) + value = Math.Max(value, minFloat); + if (Max != null && float.TryParse(Max.ToString(), out float maxFloat)) + value = Math.Min(value, maxFloat); Value = value; } diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs index 0184583d9..d0fdb6e3e 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs @@ -17,9 +17,17 @@ namespace Artemis.UI.Shared.Services /// ProfileConfiguration? SelectedProfileConfiguration { get; } + /// + /// Gets the previous selected profile configuration + /// + ProfileConfiguration? PreviousSelectedProfileConfiguration { get; } + /// /// Gets the currently selected profile - /// if the editor is closed, always equal to . + /// + /// if the editor is closed, always equal to . + /// + /// /// Profile? SelectedProfile { get; } @@ -53,6 +61,11 @@ namespace Artemis.UI.Shared.Services /// bool Playing { get; set; } + /// + /// Gets or sets a boolean indicating whether editing should be suspended + /// + bool SuspendEditing { get; set; } + /// /// Changes the selected profile by its /// @@ -214,6 +227,12 @@ namespace Artemis.UI.Shared.Services /// event EventHandler PixelsPerSecondChanged; + /// + /// Occurs when the suspend editing boolean is changed + /// + + event EventHandler SuspendEditingChanged; + /// /// Occurs when the profile preview has been updated /// diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index ed1a2d68f..f3f3626ea 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -4,7 +4,6 @@ using System.Collections.ObjectModel; using System.Linq; using System.Windows; using Artemis.Core; -using Artemis.Core.Modules; using Artemis.Core.Services; using Artemis.Storage.Entities.Profile; using Artemis.UI.Shared.Services.Models; @@ -30,6 +29,7 @@ namespace Artemis.UI.Shared.Services private TimeSpan _currentTime; private bool _doTick; private int _pixelsPerSecond; + private bool _suspendEditing; public ProfileEditorService(IKernel kernel, ILogger logger, IProfileService profileService, ICoreService coreService, IRgbService rgbService, IModuleService moduleService) { @@ -76,7 +76,7 @@ namespace Artemis.UI.Shared.Services private void Tick() { - if (SelectedProfile == null || _doTick) + if (SelectedProfile == null || _doTick || SuspendEditing) return; TickProfileElement(SelectedProfile.GetRootFolder()); @@ -106,6 +106,32 @@ namespace Artemis.UI.Shared.Services public ReadOnlyCollection RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly(); public bool Playing { get; set; } + + public bool SuspendEditing + { + get => _suspendEditing; + set + { + if (_suspendEditing == value) + return; + + _suspendEditing = value; + if (value) + { + Playing = false; + _profileService.RenderForEditor = false; + } + else + { + if (SelectedProfileConfiguration != null) + _profileService.RenderForEditor = true; + } + + OnSuspendEditingChanged(); + } + } + + public ProfileConfiguration? PreviousSelectedProfileConfiguration { get; private set; } public ProfileConfiguration? SelectedProfileConfiguration { get; private set; } public Profile? SelectedProfile => SelectedProfileConfiguration?.Profile; public RenderProfileElement? SelectedProfileElement { get; private set; } @@ -137,6 +163,9 @@ namespace Artemis.UI.Shared.Services { lock (_selectedProfileLock) { + if (SuspendEditing) + throw new ArtemisSharedUIException("Cannot change the selected profile while editing is suspended"); + if (SelectedProfileConfiguration == profileConfiguration) return; @@ -144,21 +173,30 @@ namespace Artemis.UI.Shared.Services throw new ArtemisSharedUIException("Cannot select a disposed profile"); _logger.Verbose("ChangeSelectedProfileConfiguration {profile}", profileConfiguration); + + if (SelectedProfileConfiguration != null) + SaveSelectedProfileConfiguration(); + ChangeSelectedProfileElement(null); ProfileConfigurationEventArgs profileConfigurationElementEvent = new(profileConfiguration, SelectedProfileConfiguration); // No need to deactivate the profile, if needed it will be deactivated next update if (SelectedProfileConfiguration != null) SelectedProfileConfiguration.IsBeingEdited = false; - - // The new profile may need activation + + PreviousSelectedProfileConfiguration = SelectedProfileConfiguration; SelectedProfileConfiguration = profileConfiguration; + + // The new profile may need activation if (SelectedProfileConfiguration != null) { SelectedProfileConfiguration.IsBeingEdited = true; _moduleService.SetActivationOverride(SelectedProfileConfiguration.Module); _profileService.ActivateProfile(SelectedProfileConfiguration); _profileService.RenderForEditor = true; + + if (SelectedProfileConfiguration.Profile?.LastSelectedProfileElement is RenderProfileElement renderProfileElement) + ChangeSelectedProfileElement(renderProfileElement); } else { @@ -179,6 +217,7 @@ namespace Artemis.UI.Shared.Services if (SelectedProfile == null) return; + SelectedProfile.LastSelectedProfileElement = SelectedProfileElement; _profileService.SaveProfile(SelectedProfile, true); OnSelectedProfileUpdated(new ProfileConfigurationEventArgs(SelectedProfileConfiguration)); UpdateProfilePreview(); @@ -478,6 +517,7 @@ namespace Artemis.UI.Shared.Services public event EventHandler? SelectedDataBindingChanged; public event EventHandler? CurrentTimeChanged; public event EventHandler? PixelsPerSecondChanged; + public event EventHandler? SuspendEditingChanged; public event EventHandler? ProfilePreviewUpdated; protected virtual void OnSelectedProfileChanged(ProfileConfigurationEventArgs e) @@ -510,6 +550,11 @@ namespace Artemis.UI.Shared.Services PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty); } + protected virtual void OnSuspendEditingChanged() + { + SuspendEditingChanged?.Invoke(this, EventArgs.Empty); + } + protected virtual void OnProfilePreviewUpdated() { ProfilePreviewUpdated?.Invoke(this, EventArgs.Empty); diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs index d444e16e2..8f941ef60 100644 --- a/src/Artemis.UI/Bootstrapper.cs +++ b/src/Artemis.UI/Bootstrapper.cs @@ -27,6 +27,7 @@ namespace Artemis.UI { private ApplicationStateManager _applicationStateManager; private ICoreService _core; + private ILogger _exceptionLogger; public Bootstrapper() { @@ -48,11 +49,11 @@ namespace Artemis.UI { _applicationStateManager = new ApplicationStateManager(Kernel, Args); Core.Utilities.PrepareFirstLaunch(); - - ILogger logger = Kernel.Get(); + + _exceptionLogger = Kernel.Get(); if (_applicationStateManager.FocusExistingInstance()) { - logger.Information("Shutting down because a different instance is already running."); + _exceptionLogger.Information("Shutting down because a different instance is already running."); Application.Current.Shutdown(1); return; } @@ -63,7 +64,7 @@ namespace Artemis.UI } catch (Exception ex) { - logger.Error($"Failed to set DPI-Awareness: {ex.Message}"); + _exceptionLogger.Error($"Failed to set DPI-Awareness: {ex.Message}"); } IViewManager viewManager = Kernel.Get(); @@ -76,7 +77,7 @@ namespace Artemis.UI } catch (Exception e) { - HandleFatalException(e, logger); + HandleFatalException(e); throw; } @@ -101,7 +102,7 @@ namespace Artemis.UI } catch (Exception e) { - HandleFatalException(e, logger); + HandleFatalException(e); throw; } }); @@ -132,12 +133,11 @@ namespace Artemis.UI protected override void OnUnhandledException(DispatcherUnhandledExceptionEventArgs e) { - ILogger logger = Kernel.Get(); - logger.Fatal(e.Exception, "Unhandled exception"); - - IDialogService dialogService = Kernel.Get(); try { + _exceptionLogger.Fatal(e.Exception, "Unhandled exception"); + + IDialogService dialogService = Kernel.Get(); dialogService.ShowExceptionDialog("Artemis encountered an error", e.Exception); } catch (Exception) @@ -149,9 +149,9 @@ namespace Artemis.UI e.Handled = true; } - private void HandleFatalException(Exception e, ILogger logger) + private void HandleFatalException(Exception e) { - logger.Fatal(e, "Fatal exception during initialization, shutting down."); + _exceptionLogger.Fatal(e, "Fatal exception during initialization, shutting down."); Execute.OnUIThread(() => { _applicationStateManager.DisplayException(e); diff --git a/src/Artemis.UI/Converters/LedIdToStringConverter.cs b/src/Artemis.UI/Converters/LedIdToStringConverter.cs new file mode 100644 index 000000000..154ca4bb9 --- /dev/null +++ b/src/Artemis.UI/Converters/LedIdToStringConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using RGB.NET.Core; + +namespace Artemis.UI.Converters +{ + [ValueConversion(typeof(LedId), typeof(string))] + public class LedIdToStringConverter : IValueConverter + { + #region Implementation of IValueConverter + + /// + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value?.ToString(); + } + + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (Enum.TryParse(typeof(LedId), value?.ToString(), true, out object parsedLedId)) + return parsedLedId; + return LedId.Unknown1; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Converters/UriToFileNameConverter.cs b/src/Artemis.UI/Converters/UriToFileNameConverter.cs index 435c1fec5..1118d3eec 100644 --- a/src/Artemis.UI/Converters/UriToFileNameConverter.cs +++ b/src/Artemis.UI/Converters/UriToFileNameConverter.cs @@ -5,6 +5,7 @@ using System.Windows.Data; namespace Artemis.UI.Converters { + [ValueConversion(typeof(Uri), typeof(string))] public class UriToFileNameConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/src/Artemis.UI/Events/RequestSelectSidebarItemEvent.cs b/src/Artemis.UI/Events/RequestSelectSidebarItemEvent.cs index 3241ac8f8..2892cfe61 100644 --- a/src/Artemis.UI/Events/RequestSelectSidebarItemEvent.cs +++ b/src/Artemis.UI/Events/RequestSelectSidebarItemEvent.cs @@ -1,4 +1,6 @@ -namespace Artemis.UI.Events +using Artemis.UI.Screens.Sidebar; + +namespace Artemis.UI.Events { public class RequestSelectSidebarItemEvent { @@ -7,6 +9,12 @@ DisplayName = displayName; } + public RequestSelectSidebarItemEvent(SidebarScreenViewModel viewModel) + { + ViewModel = viewModel; + } + public string DisplayName { get; } + public SidebarScreenViewModel ViewModel { get; } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml index e47af2d72..f5ee47881 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml @@ -77,7 +77,7 @@ - + @@ -444,6 +444,28 @@ Width="319" /> + + + + + + + Timeline suspended + + + The profile is currently running in normal mode and the timeline cannot be edited. + + + Press to switch between editor mode and normal mode. Auto-switching can be disabled in the options menu. + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 5aa6a6515..944111d23 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -122,6 +122,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties set => ProfileEditorService.CurrentTime = TimeSpan.FromSeconds(value / ProfileEditorService.PixelsPerSecond); } + public bool SuspendedEditing => ProfileEditorService.SuspendEditing; + public int PropertyTreeIndex { get => _propertyTreeIndex; @@ -190,6 +192,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties ProfileEditorService.SelectedProfileElementChanged += SelectedProfileEditorServiceOnSelectedProfileElementChanged; ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged; + ProfileEditorService.SuspendEditingChanged += ProfileEditorServiceOnSuspendEditingChanged; ProfileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged; ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; @@ -200,6 +203,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { ProfileEditorService.SelectedProfileElementChanged -= SelectedProfileEditorServiceOnSelectedProfileElementChanged; ProfileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged; + ProfileEditorService.SuspendEditingChanged -= ProfileEditorServiceOnSuspendEditingChanged; ProfileEditorService.SelectedDataBindingChanged -= ProfileEditorServiceOnSelectedDataBindingChanged; ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; @@ -230,6 +234,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties NotifyOfPropertyChange(nameof(TimeCaretPosition)); } + private void ProfileEditorServiceOnSuspendEditingChanged(object? sender, EventArgs e) + { + NotifyOfPropertyChange(nameof(SuspendedEditing)); + } + private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e) { NotifyOfPropertyChange(nameof(TimeCaretPosition)); @@ -559,6 +568,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e) { + if (!ProfileEditorService.Playing) + { + CoreService.FrameRendering -= CoreServiceOnFrameRendering; + return; + } + Execute.PostToUIThread(() => { TimeSpan newTime = ProfileEditorService.CurrentTime.Add(TimeSpan.FromSeconds(e.DeltaTime)); diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.xaml b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.xaml index b91b42a7f..d63979f9d 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.xaml @@ -26,6 +26,8 @@ + + @@ -60,21 +62,24 @@ s:View.ActionTarget="{Binding ProfileTreeViewModel}" /> - - + - - - @@ -94,21 +99,47 @@ Command="{s:Action Paste}" InputGestureText="Ctrl+V" /> + + + + - - - + + + + + + - @@ -121,7 +152,7 @@ Icon="{materialDesign:PackIcon Kind=Layers}" Command="{s:Action OpenUrl}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/layers" /> - @@ -129,7 +160,7 @@ Icon="{materialDesign:PackIcon Kind=Stopwatch}" Command="{s:Action OpenUrl}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/timeline" /> - @@ -138,11 +169,11 @@ Command="{s:Action OpenUrl}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/scripting" /> - - @@ -152,7 +183,7 @@