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/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..7d7a229f2 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -209,7 +209,7 @@ 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); @@ -254,11 +254,9 @@ namespace Artemis.Core.Services try { ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j]; - if (RenderForEditor) - { - if (profileConfiguration.IsBeingEdited) - profileConfiguration.Profile?.Render(canvas, SKPointI.Empty); - } + // Always render profiles being edited + if (profileConfiguration.IsBeingEdited) + profileConfiguration.Profile?.Render(canvas, SKPointI.Empty); else { // Ensure all criteria are met before rendering 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..059137ac6 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 /// diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index ed1a2d68f..c0fa9c665 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,27 @@ namespace Artemis.UI.Shared.Services public ReadOnlyCollection RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly(); public bool Playing { get; set; } + + public bool SuspendEditing + { + get => _suspendEditing; + set + { + _suspendEditing = value; + if (value) + { + Playing = false; + _profileService.RenderForEditor = false; + } + else + { + if (SelectedProfileConfiguration != null) + _profileService.RenderForEditor = true; + } + } + } + + public ProfileConfiguration? PreviousSelectedProfileConfiguration { get; private set; } public ProfileConfiguration? SelectedProfileConfiguration { get; private set; } public Profile? SelectedProfile => SelectedProfileConfiguration?.Profile; public RenderProfileElement? SelectedProfileElement { get; private set; } @@ -152,13 +173,18 @@ namespace Artemis.UI.Shared.Services SelectedProfileConfiguration.IsBeingEdited = false; // The new profile may need activation + PreviousSelectedProfileConfiguration = SelectedProfileConfiguration; SelectedProfileConfiguration = profileConfiguration; + ; 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 +205,7 @@ namespace Artemis.UI.Shared.Services if (SelectedProfile == null) return; + SelectedProfile.LastSelectedProfileElement = SelectedProfileElement; _profileService.SaveProfile(SelectedProfile, true); OnSelectedProfileUpdated(new ProfileConfigurationEventArgs(SelectedProfileConfiguration)); UpdateProfilePreview(); 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/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 5aa6a6515..9a1a3610a 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -559,6 +559,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e) { + if (!ProfileEditorService.Playing) + { + Pause(); + 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 735390a5d..5de7e7fee 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.xaml @@ -110,6 +110,24 @@ IsEnabled="{Binding HasSelectedElement}" Command="{s:Action OpenLayerPropertyScripts}"/> + + + + + + { private readonly IDebugService _debugService; private readonly IMessageService _messageService; @@ -28,15 +32,12 @@ namespace Artemis.UI.Screens.ProfileEditor private readonly ISettingsService _settingsService; private readonly ISidebarVmFactory _sidebarVmFactory; private readonly IWindowManager _windowManager; - private PluginSetting _bottomPanelsHeight; - private PluginSetting _dataModelConditionsHeight; + private readonly IEventAggregator _eventAggregator; private DisplayConditionsViewModel _displayConditionsViewModel; - private PluginSetting _elementPropertiesWidth; private LayerPropertiesViewModel _layerPropertiesViewModel; private ProfileTreeViewModel _profileTreeViewModel; private ProfileViewModel _profileViewModel; - private PluginSetting _sidePanelsWidth; - + public ProfileEditorViewModel(ProfileViewModel profileViewModel, ProfileTreeViewModel profileTreeViewModel, DisplayConditionsViewModel dataModelConditionsViewModel, @@ -48,6 +49,7 @@ namespace Artemis.UI.Screens.ProfileEditor IMessageService messageService, IDebugService debugService, IWindowManager windowManager, + IEventAggregator eventAggregator, IScriptVmFactory scriptVmFactory, ISidebarVmFactory sidebarVmFactory) { @@ -57,9 +59,10 @@ namespace Artemis.UI.Screens.ProfileEditor _messageService = messageService; _debugService = debugService; _windowManager = windowManager; + _eventAggregator = eventAggregator; _scriptVmFactory = scriptVmFactory; _sidebarVmFactory = sidebarVmFactory; - + DisplayName = "Profile Editor"; DialogService = dialogService; @@ -102,29 +105,14 @@ namespace Artemis.UI.Screens.ProfileEditor set => SetAndNotify(ref _profileViewModel, value); } - public PluginSetting SidePanelsWidth - { - get => _sidePanelsWidth; - set => SetAndNotify(ref _sidePanelsWidth, value); - } - - public PluginSetting DataModelConditionsHeight - { - get => _dataModelConditionsHeight; - set => SetAndNotify(ref _dataModelConditionsHeight, value); - } - - public PluginSetting BottomPanelsHeight - { - get => _bottomPanelsHeight; - set => SetAndNotify(ref _bottomPanelsHeight, value); - } - - public PluginSetting ElementPropertiesWidth - { - get => _elementPropertiesWidth; - set => SetAndNotify(ref _elementPropertiesWidth, value); - } + public PluginSetting SidePanelsWidth => _settingsService.GetSetting("ProfileEditor.SidePanelsWidth", new GridLength(385)); + public PluginSetting DataModelConditionsHeight => _settingsService.GetSetting("ProfileEditor.DataModelConditionsHeight", new GridLength(345)); + public PluginSetting BottomPanelsHeight => _settingsService.GetSetting("ProfileEditor.BottomPanelsHeight", new GridLength(265)); + public PluginSetting ElementPropertiesWidth => _settingsService.GetSetting("ProfileEditor.ElementPropertiesWidth", new GridLength(545)); + public PluginSetting StopOnFocusLoss => _settingsService.GetSetting("ProfileEditor.StopOnFocusLoss", true); + public PluginSetting ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); + public PluginSetting FocusSelectedLayer => _settingsService.GetSetting("ProfileEditor.FocusSelectedLayer", true); + public PluginSetting AlwaysApplyDataBindings => _settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true); public void Undo() { @@ -180,25 +168,39 @@ namespace Artemis.UI.Screens.ProfileEditor _messageService.ShowMessage("Redid profile update", "UNDO", Undo); } + #region Overrides of Screen protected override void OnInitialActivate() { + StopOnFocusLoss.AutoSave = true; + ShowDataModelValues.AutoSave = true; + FocusSelectedLayer.AutoSave = true; + AlwaysApplyDataBindings.AutoSave = true; + _profileEditorService.SelectedProfileChanged += ProfileEditorServiceOnSelectedProfileChanged; _profileEditorService.SelectedProfileElementChanged += ProfileEditorServiceOnSelectedProfileElementChanged; - LoadWorkspaceSettings(); + _eventAggregator.Subscribe(this); base.OnInitialActivate(); } protected override void OnClose() { + StopOnFocusLoss.AutoSave = false; + ShowDataModelValues.AutoSave = false; + FocusSelectedLayer.AutoSave = false; + AlwaysApplyDataBindings.AutoSave = false; + _profileEditorService.SelectedProfileChanged -= ProfileEditorServiceOnSelectedProfileChanged; _profileEditorService.SelectedProfileElementChanged -= ProfileEditorServiceOnSelectedProfileElementChanged; + _eventAggregator.Unsubscribe(this); SaveWorkspaceSettings(); _profileEditorService.ChangeSelectedProfileConfiguration(null); base.OnClose(); } + #endregion + private void ProfileEditorServiceOnSelectedProfileChanged(object sender, ProfileConfigurationEventArgs e) { NotifyOfPropertyChange(nameof(ProfileConfiguration)); @@ -208,15 +210,7 @@ namespace Artemis.UI.Screens.ProfileEditor { NotifyOfPropertyChange(nameof(HasSelectedElement)); } - - private void LoadWorkspaceSettings() - { - SidePanelsWidth = _settingsService.GetSetting("ProfileEditor.SidePanelsWidth", new GridLength(385)); - DataModelConditionsHeight = _settingsService.GetSetting("ProfileEditor.DataModelConditionsHeight", new GridLength(345)); - BottomPanelsHeight = _settingsService.GetSetting("ProfileEditor.BottomPanelsHeight", new GridLength(265)); - ElementPropertiesWidth = _settingsService.GetSetting("ProfileEditor.ElementPropertiesWidth", new GridLength(545)); - } - + private void SaveWorkspaceSettings() { SidePanelsWidth.Save(); @@ -234,7 +228,7 @@ namespace Artemis.UI.Screens.ProfileEditor { await _sidebarVmFactory.SidebarProfileConfigurationViewModel(_profileEditorService.SelectedProfileConfiguration).ViewProperties(); } - + public async Task AdaptProfile() { if (_profileEditorService.SelectedProfileConfiguration?.Profile == null) @@ -322,5 +316,19 @@ namespace Artemis.UI.Screens.ProfileEditor } #endregion + + #region Implementation of IHandle + + /// + public void Handle(MainWindowFocusChangedEvent message) + { + if (!StopOnFocusLoss.Value) + return; + + _profileEditorService.SuspendEditing = !message.IsFocused; + ProfileViewModel.SuspendedEditing = !message.IsFocused; + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml index c3388512a..843a7e09b 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml @@ -97,6 +97,11 @@ + + + diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs index 9156b86f1..e9d5bed51 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using Artemis.Core; @@ -24,8 +25,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree { _profileEditorService = profileEditorService; _profileTreeVmFactory = profileTreeVmFactory; - - CreateRootFolderViewModel(); } public TreeItemViewModel SelectedTreeItem @@ -33,7 +32,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree get => _selectedTreeItem; set { - if (!_updatingTree && SetAndNotify(ref _selectedTreeItem, value) && !_draggingTreeView) + if (!_updatingTree && SetAndNotify(ref _selectedTreeItem, value) && !_draggingTreeView) ApplySelectedTreeItem(); } } @@ -54,6 +53,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree protected override void OnInitialActivate() { Subscribe(); + CreateRootFolderViewModel(); base.OnInitialActivate(); } @@ -83,6 +83,13 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree ActiveItem = _profileTreeVmFactory.FolderViewModel(folder); _updatingTree = false; + + Execute.PostToUIThread(async () => + { + await Task.Delay(1500); + SelectedTreeItem = ActiveItem.GetAllChildren().FirstOrDefault(c => c.ProfileElement == _profileEditorService.SelectedProfileElement); + }); + } #region IDropTarget diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs index ba8de22bd..7682a7868 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs @@ -1,4 +1,5 @@ -using Artemis.Core; +using System.ComponentModel; +using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Services; @@ -19,5 +20,35 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem } public override bool SupportsChildren => true; + + public override bool IsExpanded + { + get => ((Folder) ProfileElement).IsExpanded; + set => ((Folder) ProfileElement).IsExpanded = value; + } + + private void ProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Folder.IsExpanded)) + NotifyOfPropertyChange(nameof(IsExpanded)); + } + + #region Overrides of Screen + + /// + protected override void OnInitialActivate() + { + ProfileElement.PropertyChanged += ProfileElementOnPropertyChanged; + base.OnInitialActivate(); + } + + /// + protected override void OnClose() + { + ProfileElement.PropertyChanged -= ProfileElementOnPropertyChanged; + base.OnClose(); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs index ee961a91b..d1cf96079 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs @@ -33,5 +33,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem public Layer Layer => ProfileElement as Layer; public bool ShowIcons => Layer?.LayerBrush != null; public override bool SupportsChildren => false; + + public override bool IsExpanded { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs index e4309201c..4c1cba42b 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs @@ -52,6 +52,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem public bool CanPasteElement => _profileEditorService.GetCanPasteProfileElement(); public abstract bool SupportsChildren { get; } + public abstract bool IsExpanded { get; set; } public List GetAllChildren() { diff --git a/src/Artemis.UI/Screens/ProfileEditor/SuspendedProfileEditorView.xaml b/src/Artemis.UI/Screens/ProfileEditor/SuspendedProfileEditorView.xaml new file mode 100644 index 000000000..b35e2712b --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/SuspendedProfileEditorView.xaml @@ -0,0 +1,23 @@ + + + + + Profile editing has been paused + + + Artemis has resumed regular profile playback. As soon as you focus this window, the editor will open to continue editing + ''. + + + You can disable this behaviour in the editor's option menu. + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/SuspendedProfileEditorViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/SuspendedProfileEditorViewModel.cs new file mode 100644 index 000000000..ca9525e3e --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/SuspendedProfileEditorViewModel.cs @@ -0,0 +1,53 @@ +using Artemis.Core; +using Artemis.UI.Events; +using Artemis.UI.Shared.Services; +using Stylet; + +namespace Artemis.UI.Screens.ProfileEditor +{ + public class SuspendedProfileEditorViewModel : MainScreenViewModel, IHandle + { + private readonly IEventAggregator _eventAggregator; + private readonly IProfileEditorService _profileEditorService; + private ProfileConfiguration _previousSelectedProfileConfiguration; + + public SuspendedProfileEditorViewModel(IEventAggregator eventAggregator, IProfileEditorService profileEditorService) + { + _eventAggregator = eventAggregator; + _profileEditorService = profileEditorService; + } + + public ProfileConfiguration PreviousSelectedProfileConfiguration + { + get => _previousSelectedProfileConfiguration; + set => SetAndNotify(ref _previousSelectedProfileConfiguration, value); + } + + public void Handle(MainWindowFocusChangedEvent message) + { + if (!message.IsFocused) + return; + + RootViewModel rootViewModel = (RootViewModel) Parent; + if (PreviousSelectedProfileConfiguration != null) + rootViewModel.SidebarViewModel.SelectProfileConfiguration(PreviousSelectedProfileConfiguration); + } + + #region Overrides of Screen + + protected override void OnInitialActivate() + { + PreviousSelectedProfileConfiguration = _profileEditorService.PreviousSelectedProfileConfiguration; + _eventAggregator.Subscribe(this); + base.OnInitialActivate(); + } + + protected override void OnClose() + { + _eventAggregator.Unsubscribe(this); + base.OnClose(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileView.xaml index 4e9ebdf6f..688d828a2 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileView.xaml @@ -101,6 +101,7 @@ @@ -129,25 +130,7 @@ - - - - - - Focus selected layer - - - Apply all data bindings in editor - - - - - + _alwaysApplyDataBindings; private bool _canApplyToLayer; private bool _canSelectEditTool; private BindableCollection _devices; private BindableCollection _highlightedLeds; - private PluginSetting _focusSelectedLayer; private DateTime _lastUpdate; private PanZoomViewModel _panZoomViewModel; private Layer _previousSelectedLayer; private int _previousTool; + private bool _suspendedEditing; public ProfileViewModel(IProfileEditorService profileEditorService, IRgbService rgbService, @@ -78,19 +77,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization get => _highlightedLeds; set => SetAndNotify(ref _highlightedLeds, value); } - - public PluginSetting AlwaysApplyDataBindings - { - get => _alwaysApplyDataBindings; - set => SetAndNotify(ref _alwaysApplyDataBindings, value); - } - - public PluginSetting FocusSelectedLayer - { - get => _focusSelectedLayer; - set => SetAndNotify(ref _focusSelectedLayer, value); - } - + public VisualizationToolViewModel ActiveToolViewModel { get => _activeToolViewModel; @@ -132,9 +119,21 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization set => SetAndNotify(ref _canApplyToLayer, value); } + public bool SuspendedEditing + { + get => _suspendedEditing; + set => SetAndNotify(ref _suspendedEditing, value); + } + protected override void OnInitialActivate() { - PanZoomViewModel = new PanZoomViewModel {LimitToZero = false}; + PanZoomViewModel = new PanZoomViewModel + { + LimitToZero = false, + PanX = _settingsService.GetSetting("ProfileEditor.PanX", 0d).Value, + PanY = _settingsService.GetSetting("ProfileEditor.PanY", 0d).Value, + Zoom = _settingsService.GetSetting("ProfileEditor.Zoom", 0d).Value + }; Devices = new BindableCollection(); HighlightedLeds = new BindableCollection(); @@ -143,14 +142,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization ActivateToolByIndex(0); ApplyActiveProfile(); - - AlwaysApplyDataBindings = _settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true); - FocusSelectedLayer = _settingsService.GetSetting("ProfileEditor.FocusSelectedLayer", true); - + _lastUpdate = DateTime.Now; _coreService.FrameRendered += OnFrameRendered; - FocusSelectedLayer.SettingChanged += HighlightSelectedLayerOnSettingChanged; _rgbService.DeviceAdded += RgbServiceOnDevicesModified; _rgbService.DeviceRemoved += RgbServiceOnDevicesModified; _profileEditorService.SelectedProfileChanged += OnSelectedProfileChanged; @@ -163,7 +158,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization protected override void OnClose() { _coreService.FrameRendered -= OnFrameRendered; - FocusSelectedLayer.SettingChanged -= HighlightSelectedLayerOnSettingChanged; _rgbService.DeviceAdded -= RgbServiceOnDevicesModified; _rgbService.DeviceRemoved -= RgbServiceOnDevicesModified; _profileEditorService.SelectedProfileChanged -= OnSelectedProfileChanged; @@ -172,9 +166,13 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization if (_previousSelectedLayer != null) _previousSelectedLayer.LayerBrushUpdated -= SelectedLayerOnLayerBrushUpdated; - AlwaysApplyDataBindings.Save(); - FocusSelectedLayer.Save(); - + _settingsService.GetSetting("ProfileEditor.PanX", 0d).Value = PanZoomViewModel.PanX; + _settingsService.GetSetting("ProfileEditor.PanX", 0d).Save(); + _settingsService.GetSetting("ProfileEditor.PanY", 0d).Value = PanZoomViewModel.PanY; + _settingsService.GetSetting("ProfileEditor.PanY", 0d).Save(); + _settingsService.GetSetting("ProfileEditor.Zoom", 0d).Value = PanZoomViewModel.Zoom; + _settingsService.GetSetting("ProfileEditor.Zoom", 0d).Save(); + base.OnClose(); } @@ -210,7 +208,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization private void UpdateLedsDimStatus() { HighlightedLeds.Clear(); - if (FocusSelectedLayer.Value && _profileEditorService.SelectedProfileElement is Layer layer) + if (_profileEditorService.SelectedProfileElement is Layer layer) HighlightedLeds.AddRange(layer.Leds); } @@ -308,7 +306,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization TimeSpan delta = DateTime.Now - _lastUpdate; _lastUpdate = DateTime.Now; - if (!AlwaysApplyDataBindings.Value || _profileEditorService.SelectedProfile == null) + if (!_settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true).Value || _profileEditorService.SelectedProfile == null) return; foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllFolders() diff --git a/src/Artemis.UI/Screens/RootViewModel.cs b/src/Artemis.UI/Screens/RootViewModel.cs index 8301080b0..902b447f2 100644 --- a/src/Artemis.UI/Screens/RootViewModel.cs +++ b/src/Artemis.UI/Screens/RootViewModel.cs @@ -15,6 +15,7 @@ using MaterialDesignExtensions.Controls; using MaterialDesignThemes.Wpf; using Ninject; using Stylet; +using Stylet.Xaml; using Constants = Artemis.Core.Constants; namespace Artemis.UI.Screens @@ -90,6 +91,8 @@ namespace Artemis.UI.Screens { if (!_lostFocus) return; + if (!((MaterialWindow)View).IsActive) + return; _lostFocus = false; _eventAggregator.Publish(new MainWindowFocusChangedEvent(true)); diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs index 3407d7e07..2e4a4f734 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs @@ -126,7 +126,12 @@ namespace Artemis.UI.Screens.Sidebar public void Handle(RequestSelectSidebarItemEvent message) { - SidebarScreenViewModel requested = SidebarScreens.FirstOrDefault(s => s.DisplayName == message.DisplayName); + SidebarScreenViewModel requested = null; + if (message.DisplayName != null) + requested = SidebarScreens.FirstOrDefault(s => s.DisplayName == message.DisplayName); + else + requested = message.ViewModel; + if (requested != null) SelectedSidebarScreen = requested; }