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

Merge branch 'development'

This commit is contained in:
Robert 2021-07-03 15:31:02 +02:00
commit 600b20e5be
34 changed files with 643 additions and 258 deletions

View File

@ -14,6 +14,8 @@ namespace Artemis.Core
/// </summary> /// </summary>
public sealed class Folder : RenderProfileElement public sealed class Folder : RenderProfileElement
{ {
private bool _isExpanded;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="Folder" /> class and adds itself to the child collection of the provided /// Creates a new instance of the <see cref="Folder" /> class and adds itself to the child collection of the provided
/// <paramref name="parent" /> /// <paramref name="parent" />
@ -46,6 +48,7 @@ namespace Artemis.Core
Profile = profile; Profile = profile;
Parent = parent; Parent = parent;
Name = folderEntity.Name; Name = folderEntity.Name;
IsExpanded = folderEntity.IsExpanded;
Suspended = folderEntity.Suspended; Suspended = folderEntity.Suspended;
Order = folderEntity.Order; Order = folderEntity.Order;
@ -57,6 +60,15 @@ namespace Artemis.Core
/// </summary> /// </summary>
public bool IsRootFolder => Parent == Profile; public bool IsRootFolder => Parent == Profile;
/// <summary>
/// Gets or sets a boolean indicating whether this folder is expanded
/// </summary>
public bool IsExpanded
{
get => _isExpanded;
set => SetAndNotify(ref _isExpanded, value);
}
/// <summary> /// <summary>
/// Gets the folder entity this folder uses for persistent storage /// Gets the folder entity this folder uses for persistent storage
/// </summary> /// </summary>
@ -72,8 +84,10 @@ namespace Artemis.Core
{ {
List<ILayerProperty> result = new(); List<ILayerProperty> result = new();
foreach (BaseLayerEffect layerEffect in LayerEffects) foreach (BaseLayerEffect layerEffect in LayerEffects)
{
if (layerEffect.BaseProperties != null) if (layerEffect.BaseProperties != null)
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties()); result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
}
return result; return result;
} }
@ -151,27 +165,6 @@ namespace Artemis.Core
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; 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 #region Rendering
/// <inheritdoc /> /// <inheritdoc />
@ -209,7 +202,7 @@ namespace Artemis.Core
canvas.SaveLayer(layerPaint); canvas.SaveLayer(layerPaint);
canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y); 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 // 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--) for (int index = Children.Count - 1; index > -1; index--)
Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top)); Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top));
@ -229,57 +222,6 @@ namespace Artemis.Core
#endregion #endregion
/// <inheritdoc />
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();
}
/// <inheritdoc /> /// <inheritdoc />
public override void Enable() public override void Enable()
{ {
@ -320,18 +262,87 @@ namespace Artemis.Core
Enabled = false; Enabled = false;
} }
#region Events
/// <summary> /// <summary>
/// Occurs when a property affecting the rendering properties of this folder has been updated /// Occurs when a property affecting the rendering properties of this folder has been updated
/// </summary> /// </summary>
public event EventHandler? RenderPropertiesUpdated; public event EventHandler? RenderPropertiesUpdated;
/// <inheritdoc />
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() private void OnRenderPropertiesUpdated()
{ {
RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty); RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty);
} }
#endregion
} }
} }

View File

@ -14,6 +14,7 @@ namespace Artemis.Core
{ {
private readonly object _lock = new(); private readonly object _lock = new();
private bool _isFreshImport; private bool _isFreshImport;
private ProfileElement? _lastSelectedProfileElement;
internal Profile(ProfileConfiguration configuration, ProfileEntity profileEntity) : base(null!) internal Profile(ProfileConfiguration configuration, ProfileEntity profileEntity) : base(null!)
{ {
@ -60,6 +61,15 @@ namespace Artemis.Core
set => SetAndNotify(ref _isFreshImport, value); set => SetAndNotify(ref _isFreshImport, value);
} }
/// <summary>
/// Gets or sets the last selected profile element of this profile
/// </summary>
public ProfileElement? LastSelectedProfileElement
{
get => _lastSelectedProfileElement;
set => SetAndNotify(ref _lastSelectedProfileElement, value);
}
/// <summary> /// <summary>
/// Gets the profile entity this profile uses for persistent storage /// Gets the profile entity this profile uses for persistent storage
/// </summary> /// </summary>
@ -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) foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations)
scriptConfiguration.Script?.Dispose(); scriptConfiguration.Script?.Dispose();
ScriptConfigurations.Clear(); ScriptConfigurations.Clear();
@ -201,6 +220,7 @@ namespace Artemis.Core
ProfileEntity.Id = EntityId; ProfileEntity.Id = EntityId;
ProfileEntity.Name = Configuration.Name; ProfileEntity.Name = Configuration.Name;
ProfileEntity.IsFreshImport = IsFreshImport; ProfileEntity.IsFreshImport = IsFreshImport;
ProfileEntity.LastSelectedProfileElement = LastSelectedProfileElement?.EntityId ?? Guid.Empty;
foreach (ProfileElement profileElement in Children) foreach (ProfileElement profileElement in Children)
profileElement.Save(); profileElement.Save();

View File

@ -397,7 +397,7 @@ namespace Artemis.Core
if (conditionMet && !DisplayConditionMet && Timeline.IsFinished) if (conditionMet && !DisplayConditionMet && Timeline.IsFinished)
Timeline.JumpToStart(); Timeline.JumpToStart();
// If regular conditions are no longer met, jump to the end segment if stop mode requires it // 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(); Timeline.JumpToEndSegment();
} }
else if (conditionMet) else if (conditionMet)

View File

@ -179,7 +179,8 @@ namespace Artemis.Core
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("ProfileConfiguration"); throw new ObjectDisposedException("ProfileConfiguration");
if (IsBeingEdited)
return true;
if (Category.IsSuspended || IsSuspended || IsMissingModule) if (Category.IsSuspended || IsSuspended || IsMissingModule)
return false; return false;

View File

@ -209,14 +209,15 @@ namespace Artemis.Core.Services
ProcessPendingKeyEvents(profileConfiguration); ProcessPendingKeyEvents(profileConfiguration);
// Profiles being edited are updated at their own leisure // Profiles being edited are updated at their own leisure
if (profileConfiguration.IsBeingEdited) if (profileConfiguration.IsBeingEdited && RenderForEditor)
continue; continue;
bool shouldBeActive = profileConfiguration.ShouldBeActive(false); bool shouldBeActive = profileConfiguration.ShouldBeActive(false);
if (shouldBeActive) if (shouldBeActive)
{ {
profileConfiguration.Update(); profileConfiguration.Update();
shouldBeActive = profileConfiguration.ActivationConditionMet; if (!profileConfiguration.IsBeingEdited)
shouldBeActive = profileConfiguration.ActivationConditionMet;
} }
try try
@ -245,6 +246,16 @@ namespace Artemis.Core.Services
{ {
lock (_profileCategories) 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 // 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--) for (int i = _profileCategories.Count - 1; i > -1; i--)
{ {
@ -254,17 +265,9 @@ namespace Artemis.Core.Services
try try
{ {
ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j]; ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j];
if (RenderForEditor) // Ensure all criteria are met before rendering
{ if (!profileConfiguration.IsSuspended && !profileConfiguration.IsMissingModule && profileConfiguration.ActivationConditionMet)
if (profileConfiguration.IsBeingEdited) profileConfiguration.Profile?.Render(canvas, SKPointI.Empty);
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);
}
} }
catch (Exception e) catch (Exception e)
{ {
@ -396,13 +399,6 @@ namespace Artemis.Core.Services
} }
} }
/// <summary>
/// Creates a new profile configuration and adds it to the provided <see cref="ProfileCategory" />
/// </summary>
/// <param name="category">The profile category to add the profile to</param>
/// <param name="name">The name of the new profile configuration</param>
/// <param name="icon">The icon of the new profile configuration</param>
/// <returns>The newly created profile configuration</returns>
public ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon) public ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon)
{ {
ProfileConfiguration configuration = new(category, name, icon); ProfileConfiguration configuration = new(category, name, icon);
@ -414,10 +410,6 @@ namespace Artemis.Core.Services
return configuration; return configuration;
} }
/// <summary>
/// Removes the provided profile configuration from the <see cref="ProfileCategory" />
/// </summary>
/// <param name="profileConfiguration"></param>
public void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration) public void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration)
{ {
profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration); profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration);

View File

@ -16,6 +16,7 @@ namespace Artemis.Storage.Entities.Profile
public int Order { get; set; } public int Order { get; set; }
public string Name { get; set; } public string Name { get; set; }
public bool IsExpanded { get; set; }
public bool Suspended { get; set; } public bool Suspended { get; set; }
[BsonRef("ProfileEntity")] [BsonRef("ProfileEntity")]

View File

@ -18,6 +18,7 @@ namespace Artemis.Storage.Entities.Profile
public string Name { get; set; } public string Name { get; set; }
public bool IsFreshImport { get; set; } public bool IsFreshImport { get; set; }
public Guid LastSelectedProfileElement { get; set; }
public List<FolderEntity> Folders { get; set; } public List<FolderEntity> Folders { get; set; }
public List<LayerEntity> Layers { get; set; } public List<LayerEntity> Layers { get; set; }

View File

@ -27,12 +27,12 @@ namespace Artemis.UI.Shared
/// <summary> /// <summary>
/// Gets or sets the minimum value /// Gets or sets the minimum value
/// </summary> /// </summary>
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));
/// <summary> /// <summary>
/// Gets or sets the maximum value /// Gets or sets the maximum value
/// </summary> /// </summary>
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));
/// <summary> /// <summary>
/// Occurs when the value has changed /// Occurs when the value has changed
@ -90,18 +90,18 @@ namespace Artemis.UI.Shared
/// <summary> /// <summary>
/// Gets or sets the minimum value /// Gets or sets the minimum value
/// </summary> /// </summary>
public float? Min public object? Min
{ {
get => (float?) GetValue(MinProperty); get => (object?) GetValue(MinProperty);
set => SetValue(MinProperty, value); set => SetValue(MinProperty, value);
} }
/// <summary> /// <summary>
/// Gets or sets the maximum value /// Gets or sets the maximum value
/// </summary> /// </summary>
public float? Max public object? Max
{ {
get => (float?) GetValue(MaxProperty); get => (object?) GetValue(MaxProperty);
set => SetValue(MaxProperty, value); set => SetValue(MaxProperty, value);
} }
@ -207,10 +207,11 @@ namespace Artemis.UI.Shared
stepSize = stepSize * 10; stepSize = stepSize * 10;
float value = (float) RoundToNearestOf(startValue + stepSize * (x - startX), stepSize); float value = (float) RoundToNearestOf(startValue + stepSize * (x - startX), stepSize);
if (Min != null)
value = Math.Max(value, Min.Value); if (Min != null && float.TryParse(Min.ToString(), out float minFloat))
if (Max != null) value = Math.Max(value, minFloat);
value = Math.Min(value, Max.Value); if (Max != null && float.TryParse(Max.ToString(), out float maxFloat))
value = Math.Min(value, maxFloat);
Value = value; Value = value;
} }

View File

@ -17,9 +17,17 @@ namespace Artemis.UI.Shared.Services
/// </summary> /// </summary>
ProfileConfiguration? SelectedProfileConfiguration { get; } ProfileConfiguration? SelectedProfileConfiguration { get; }
/// <summary>
/// Gets the previous selected profile configuration
/// </summary>
ProfileConfiguration? PreviousSelectedProfileConfiguration { get; }
/// <summary> /// <summary>
/// Gets the currently selected profile /// Gets the currently selected profile
/// <para><see langword="null" /> if the editor is closed, always equal to <see cref="SelectedProfileConfiguration" />.<see cref="Profile" /></para> /// <para>
/// <see langword="null" /> if the editor is closed, always equal to <see cref="SelectedProfileConfiguration" />.
/// <see cref="Profile" />
/// </para>
/// </summary> /// </summary>
Profile? SelectedProfile { get; } Profile? SelectedProfile { get; }
@ -53,6 +61,11 @@ namespace Artemis.UI.Shared.Services
/// </summary> /// </summary>
bool Playing { get; set; } bool Playing { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether editing should be suspended
/// </summary>
bool SuspendEditing { get; set; }
/// <summary> /// <summary>
/// Changes the selected profile by its <see cref="ProfileConfiguration" /> /// Changes the selected profile by its <see cref="ProfileConfiguration" />
/// </summary> /// </summary>
@ -214,6 +227,12 @@ namespace Artemis.UI.Shared.Services
/// </summary> /// </summary>
event EventHandler PixelsPerSecondChanged; event EventHandler PixelsPerSecondChanged;
/// <summary>
/// Occurs when the suspend editing boolean is changed
/// </summary>
event EventHandler SuspendEditingChanged;
/// <summary> /// <summary>
/// Occurs when the profile preview has been updated /// Occurs when the profile preview has been updated
/// </summary> /// </summary>

View File

@ -4,7 +4,6 @@ using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Artemis.UI.Shared.Services.Models; using Artemis.UI.Shared.Services.Models;
@ -30,6 +29,7 @@ namespace Artemis.UI.Shared.Services
private TimeSpan _currentTime; private TimeSpan _currentTime;
private bool _doTick; private bool _doTick;
private int _pixelsPerSecond; private int _pixelsPerSecond;
private bool _suspendEditing;
public ProfileEditorService(IKernel kernel, ILogger logger, IProfileService profileService, ICoreService coreService, IRgbService rgbService, IModuleService moduleService) 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() private void Tick()
{ {
if (SelectedProfile == null || _doTick) if (SelectedProfile == null || _doTick || SuspendEditing)
return; return;
TickProfileElement(SelectedProfile.GetRootFolder()); TickProfileElement(SelectedProfile.GetRootFolder());
@ -106,6 +106,32 @@ namespace Artemis.UI.Shared.Services
public ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly(); public ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly();
public bool Playing { get; set; } 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 ProfileConfiguration? SelectedProfileConfiguration { get; private set; }
public Profile? SelectedProfile => SelectedProfileConfiguration?.Profile; public Profile? SelectedProfile => SelectedProfileConfiguration?.Profile;
public RenderProfileElement? SelectedProfileElement { get; private set; } public RenderProfileElement? SelectedProfileElement { get; private set; }
@ -137,6 +163,9 @@ namespace Artemis.UI.Shared.Services
{ {
lock (_selectedProfileLock) lock (_selectedProfileLock)
{ {
if (SuspendEditing)
throw new ArtemisSharedUIException("Cannot change the selected profile while editing is suspended");
if (SelectedProfileConfiguration == profileConfiguration) if (SelectedProfileConfiguration == profileConfiguration)
return; return;
@ -144,21 +173,30 @@ namespace Artemis.UI.Shared.Services
throw new ArtemisSharedUIException("Cannot select a disposed profile"); throw new ArtemisSharedUIException("Cannot select a disposed profile");
_logger.Verbose("ChangeSelectedProfileConfiguration {profile}", profileConfiguration); _logger.Verbose("ChangeSelectedProfileConfiguration {profile}", profileConfiguration);
if (SelectedProfileConfiguration != null)
SaveSelectedProfileConfiguration();
ChangeSelectedProfileElement(null); ChangeSelectedProfileElement(null);
ProfileConfigurationEventArgs profileConfigurationElementEvent = new(profileConfiguration, SelectedProfileConfiguration); ProfileConfigurationEventArgs profileConfigurationElementEvent = new(profileConfiguration, SelectedProfileConfiguration);
// No need to deactivate the profile, if needed it will be deactivated next update // No need to deactivate the profile, if needed it will be deactivated next update
if (SelectedProfileConfiguration != null) if (SelectedProfileConfiguration != null)
SelectedProfileConfiguration.IsBeingEdited = false; SelectedProfileConfiguration.IsBeingEdited = false;
// The new profile may need activation PreviousSelectedProfileConfiguration = SelectedProfileConfiguration;
SelectedProfileConfiguration = profileConfiguration; SelectedProfileConfiguration = profileConfiguration;
// The new profile may need activation
if (SelectedProfileConfiguration != null) if (SelectedProfileConfiguration != null)
{ {
SelectedProfileConfiguration.IsBeingEdited = true; SelectedProfileConfiguration.IsBeingEdited = true;
_moduleService.SetActivationOverride(SelectedProfileConfiguration.Module); _moduleService.SetActivationOverride(SelectedProfileConfiguration.Module);
_profileService.ActivateProfile(SelectedProfileConfiguration); _profileService.ActivateProfile(SelectedProfileConfiguration);
_profileService.RenderForEditor = true; _profileService.RenderForEditor = true;
if (SelectedProfileConfiguration.Profile?.LastSelectedProfileElement is RenderProfileElement renderProfileElement)
ChangeSelectedProfileElement(renderProfileElement);
} }
else else
{ {
@ -179,6 +217,7 @@ namespace Artemis.UI.Shared.Services
if (SelectedProfile == null) if (SelectedProfile == null)
return; return;
SelectedProfile.LastSelectedProfileElement = SelectedProfileElement;
_profileService.SaveProfile(SelectedProfile, true); _profileService.SaveProfile(SelectedProfile, true);
OnSelectedProfileUpdated(new ProfileConfigurationEventArgs(SelectedProfileConfiguration)); OnSelectedProfileUpdated(new ProfileConfigurationEventArgs(SelectedProfileConfiguration));
UpdateProfilePreview(); UpdateProfilePreview();
@ -478,6 +517,7 @@ namespace Artemis.UI.Shared.Services
public event EventHandler? SelectedDataBindingChanged; public event EventHandler? SelectedDataBindingChanged;
public event EventHandler? CurrentTimeChanged; public event EventHandler? CurrentTimeChanged;
public event EventHandler? PixelsPerSecondChanged; public event EventHandler? PixelsPerSecondChanged;
public event EventHandler? SuspendEditingChanged;
public event EventHandler? ProfilePreviewUpdated; public event EventHandler? ProfilePreviewUpdated;
protected virtual void OnSelectedProfileChanged(ProfileConfigurationEventArgs e) protected virtual void OnSelectedProfileChanged(ProfileConfigurationEventArgs e)
@ -510,6 +550,11 @@ namespace Artemis.UI.Shared.Services
PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty); PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty);
} }
protected virtual void OnSuspendEditingChanged()
{
SuspendEditingChanged?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnProfilePreviewUpdated() protected virtual void OnProfilePreviewUpdated()
{ {
ProfilePreviewUpdated?.Invoke(this, EventArgs.Empty); ProfilePreviewUpdated?.Invoke(this, EventArgs.Empty);

View File

@ -27,6 +27,7 @@ namespace Artemis.UI
{ {
private ApplicationStateManager _applicationStateManager; private ApplicationStateManager _applicationStateManager;
private ICoreService _core; private ICoreService _core;
private ILogger _exceptionLogger;
public Bootstrapper() public Bootstrapper()
{ {
@ -48,11 +49,11 @@ namespace Artemis.UI
{ {
_applicationStateManager = new ApplicationStateManager(Kernel, Args); _applicationStateManager = new ApplicationStateManager(Kernel, Args);
Core.Utilities.PrepareFirstLaunch(); Core.Utilities.PrepareFirstLaunch();
ILogger logger = Kernel.Get<ILogger>(); _exceptionLogger = Kernel.Get<ILogger>();
if (_applicationStateManager.FocusExistingInstance()) 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); Application.Current.Shutdown(1);
return; return;
} }
@ -63,7 +64,7 @@ namespace Artemis.UI
} }
catch (Exception ex) 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<IViewManager>(); IViewManager viewManager = Kernel.Get<IViewManager>();
@ -76,7 +77,7 @@ namespace Artemis.UI
} }
catch (Exception e) catch (Exception e)
{ {
HandleFatalException(e, logger); HandleFatalException(e);
throw; throw;
} }
@ -101,7 +102,7 @@ namespace Artemis.UI
} }
catch (Exception e) catch (Exception e)
{ {
HandleFatalException(e, logger); HandleFatalException(e);
throw; throw;
} }
}); });
@ -132,12 +133,11 @@ namespace Artemis.UI
protected override void OnUnhandledException(DispatcherUnhandledExceptionEventArgs e) protected override void OnUnhandledException(DispatcherUnhandledExceptionEventArgs e)
{ {
ILogger logger = Kernel.Get<ILogger>();
logger.Fatal(e.Exception, "Unhandled exception");
IDialogService dialogService = Kernel.Get<IDialogService>();
try try
{ {
_exceptionLogger.Fatal(e.Exception, "Unhandled exception");
IDialogService dialogService = Kernel.Get<IDialogService>();
dialogService.ShowExceptionDialog("Artemis encountered an error", e.Exception); dialogService.ShowExceptionDialog("Artemis encountered an error", e.Exception);
} }
catch (Exception) catch (Exception)
@ -149,9 +149,9 @@ namespace Artemis.UI
e.Handled = true; 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(() => Execute.OnUIThread(() =>
{ {
_applicationStateManager.DisplayException(e); _applicationStateManager.DisplayException(e);

View File

@ -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
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value?.ToString();
}
/// <inheritdoc />
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
}
}

View File

@ -5,6 +5,7 @@ using System.Windows.Data;
namespace Artemis.UI.Converters namespace Artemis.UI.Converters
{ {
[ValueConversion(typeof(Uri), typeof(string))]
public class UriToFileNameConverter : IValueConverter public class UriToFileNameConverter : IValueConverter
{ {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

View File

@ -1,4 +1,6 @@
namespace Artemis.UI.Events using Artemis.UI.Screens.Sidebar;
namespace Artemis.UI.Events
{ {
public class RequestSelectSidebarItemEvent public class RequestSelectSidebarItemEvent
{ {
@ -7,6 +9,12 @@
DisplayName = displayName; DisplayName = displayName;
} }
public RequestSelectSidebarItemEvent(SidebarScreenViewModel viewModel)
{
ViewModel = viewModel;
}
public string DisplayName { get; } public string DisplayName { get; }
public SidebarScreenViewModel ViewModel { get; }
} }
} }

View File

@ -77,7 +77,7 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1.5*" /> <ColumnDefinition Width="1.5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- Left side --> <!-- Left side -->
<Grid Grid.Column="0"> <Grid Grid.Column="0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
@ -444,6 +444,28 @@
Width="319" /> Width="319" />
</Grid> </Grid>
<!-- Suspended overlay -->
<Border Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="3"
Grid.RowSpan="2"
Panel.ZIndex="3"
Background="#CD353535"
Visibility="{Binding SuspendedEditing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="16">
<materialDesign:PackIcon Kind="TimerOffOutline" Width="150" Height="150" HorizontalAlignment="Center" />
<TextBlock Style="{StaticResource MaterialDesignHeadline4TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0 10">
Timeline suspended
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center">
The profile is currently running in normal mode and the timeline cannot be edited.
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center">
Press <Run Text="F5" FontWeight="Bold"/> to switch between editor mode and normal mode. Auto-switching can be disabled in the options menu.
</TextBlock>
</StackPanel>
</Border>
</Grid> </Grid>
</materialDesign:DialogHost> </materialDesign:DialogHost>
</UserControl> </UserControl>

View File

@ -122,6 +122,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
set => ProfileEditorService.CurrentTime = TimeSpan.FromSeconds(value / ProfileEditorService.PixelsPerSecond); set => ProfileEditorService.CurrentTime = TimeSpan.FromSeconds(value / ProfileEditorService.PixelsPerSecond);
} }
public bool SuspendedEditing => ProfileEditorService.SuspendEditing;
public int PropertyTreeIndex public int PropertyTreeIndex
{ {
get => _propertyTreeIndex; get => _propertyTreeIndex;
@ -190,6 +192,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
ProfileEditorService.SelectedProfileElementChanged += SelectedProfileEditorServiceOnSelectedProfileElementChanged; ProfileEditorService.SelectedProfileElementChanged += SelectedProfileEditorServiceOnSelectedProfileElementChanged;
ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged; ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged;
ProfileEditorService.SuspendEditingChanged += ProfileEditorServiceOnSuspendEditingChanged;
ProfileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged; ProfileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged;
ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
@ -200,6 +203,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
{ {
ProfileEditorService.SelectedProfileElementChanged -= SelectedProfileEditorServiceOnSelectedProfileElementChanged; ProfileEditorService.SelectedProfileElementChanged -= SelectedProfileEditorServiceOnSelectedProfileElementChanged;
ProfileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged; ProfileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged;
ProfileEditorService.SuspendEditingChanged -= ProfileEditorServiceOnSuspendEditingChanged;
ProfileEditorService.SelectedDataBindingChanged -= ProfileEditorServiceOnSelectedDataBindingChanged; ProfileEditorService.SelectedDataBindingChanged -= ProfileEditorServiceOnSelectedDataBindingChanged;
ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
@ -230,6 +234,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
NotifyOfPropertyChange(nameof(TimeCaretPosition)); NotifyOfPropertyChange(nameof(TimeCaretPosition));
} }
private void ProfileEditorServiceOnSuspendEditingChanged(object? sender, EventArgs e)
{
NotifyOfPropertyChange(nameof(SuspendedEditing));
}
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e) private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
{ {
NotifyOfPropertyChange(nameof(TimeCaretPosition)); NotifyOfPropertyChange(nameof(TimeCaretPosition));
@ -559,6 +568,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e) private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e)
{ {
if (!ProfileEditorService.Playing)
{
CoreService.FrameRendering -= CoreServiceOnFrameRendering;
return;
}
Execute.PostToUIThread(() => Execute.PostToUIThread(() =>
{ {
TimeSpan newTime = ProfileEditorService.CurrentTime.Add(TimeSpan.FromSeconds(e.DeltaTime)); TimeSpan newTime = ProfileEditorService.CurrentTime.Add(TimeSpan.FromSeconds(e.DeltaTime));

View File

@ -26,6 +26,8 @@
<UserControl.InputBindings> <UserControl.InputBindings>
<KeyBinding Command="{s:Action Undo}" Modifiers="Control" Key="Z" /> <KeyBinding Command="{s:Action Undo}" Modifiers="Control" Key="Z" />
<KeyBinding Command="{s:Action Redo}" Modifiers="Control" Key="Y" /> <KeyBinding Command="{s:Action Redo}" Modifiers="Control" Key="Y" />
<KeyBinding Command="{s:Action ToggleSuspend}" Key="F5" />
<KeyBinding Command="{s:Action ToggleAutoSuspend}" Modifiers="Shift" Key="F5" />
</UserControl.InputBindings> </UserControl.InputBindings>
<Grid ClipToBounds="True"> <Grid ClipToBounds="True">
@ -60,21 +62,24 @@
s:View.ActionTarget="{Binding ProfileTreeViewModel}" /> s:View.ActionTarget="{Binding ProfileTreeViewModel}" />
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Header="View properties" <MenuItem Header="View Properties"
Icon="{materialDesign:PackIcon Kind=Settings}" Icon="{materialDesign:PackIcon Kind=Settings}"
Command="{s:Action ViewProperties}"/> Command="{s:Action ViewProperties}"/>
<MenuItem Header="Suspend profile" <MenuItem Header="Adapt Profile"
Icon="{materialDesign:PackIcon Kind=Magic}"
Command="{s:Action AdaptProfile}"/>
<MenuItem Header="Suspend Profile"
IsCheckable="True" IsCheckable="True"
IsChecked="{Binding ProfileConfiguration.IsSuspended}" /> IsChecked="{Binding ProfileConfiguration.IsSuspended}" />
<Separator /> <Separator />
<MenuItem Header="Export profile" <MenuItem Header="Export Profile"
Icon="{materialDesign:PackIcon Kind=Export}" Icon="{materialDesign:PackIcon Kind=Export}"
Command="{s:Action ExportProfile}" /> Command="{s:Action ExportProfile}" />
<MenuItem Header="Duplicate profile" <MenuItem Header="Duplicate Profile"
Icon="{materialDesign:PackIcon Kind=ContentDuplicate}" Icon="{materialDesign:PackIcon Kind=ContentDuplicate}"
Command="{s:Action DuplicateProfile}" /> Command="{s:Action DuplicateProfile}" />
<Separator /> <Separator />
<MenuItem Header="Delete profile" <MenuItem Header="Delete Profile"
Icon="{materialDesign:PackIcon Kind=Trash}" Icon="{materialDesign:PackIcon Kind=Trash}"
Command="{s:Action DeleteProfile}" /> Command="{s:Action DeleteProfile}" />
</MenuItem> </MenuItem>
@ -94,21 +99,47 @@
Command="{s:Action Paste}" Command="{s:Action Paste}"
InputGestureText="Ctrl+V" /> InputGestureText="Ctrl+V" />
</MenuItem> </MenuItem>
<MenuItem Header="_Run">
<MenuItem Header="_Switch run mode"
Icon="{materialDesign:PackIcon Kind=SwapHorizontal}"
Command="{s:Action ToggleSuspend}"
InputGestureText="F5" />
<MenuItem Header="Run Profile on Focus Loss"
ToolTip="If enabled, run mode is set to normal on focus loss"
IsCheckable="True"
IsChecked="{Binding StopOnFocusLoss.Value}"
InputGestureText="Shift+F5" />
</MenuItem>
<MenuItem Header="_Scripting" IsEnabled="False"> <MenuItem Header="_Scripting" IsEnabled="False">
<MenuItem Header="_Profile scripts" <MenuItem Header="_Profile Scripts"
Icon="{materialDesign:PackIcon Kind=BookEdit}" Icon="{materialDesign:PackIcon Kind=BookEdit}"
Command="{s:Action OpenProfileScripts}"/> Command="{s:Action OpenProfileScripts}"/>
<MenuItem Header="_Layer scripts" <MenuItem Header="_Layer Scripts"
Icon="{materialDesign:PackIcon Kind=Layers}" Icon="{materialDesign:PackIcon Kind=Layers}"
IsEnabled="{Binding HasSelectedElement}" IsEnabled="{Binding HasSelectedElement}"
Command="{s:Action OpenLayerScripts}"/> Command="{s:Action OpenLayerScripts}"/>
<MenuItem Header="_Property scripts" <MenuItem Header="_Property Scripts"
Icon="{materialDesign:PackIcon Kind=FormTextbox}" Icon="{materialDesign:PackIcon Kind=FormTextbox}"
IsEnabled="{Binding HasSelectedElement}" IsEnabled="{Binding HasSelectedElement}"
Command="{s:Action OpenLayerPropertyScripts}"/> Command="{s:Action OpenLayerPropertyScripts}"/>
</MenuItem> </MenuItem>
<MenuItem Header="_Options">
<MenuItem Header="Focus Selected Layer"
ToolTip="If enabled, displays only the layer you currently have selected"
IsCheckable="True"
IsChecked="{Binding FocusSelectedLayer.Value}"
IsEnabled="False"/>
<MenuItem Header="Display Data Model Values"
IsCheckable="True"
IsChecked="{Binding ShowDataModelValues.Value}"/>
<MenuItem Header="Apply All Data Bindings During Edit"
ToolTip="If enabled, updates all data bindings instead of only the one you are editing"
IsCheckable="True"
IsChecked="{Binding AlwaysApplyDataBindings.Value}"/>
</MenuItem>
<MenuItem Header="_Help"> <MenuItem Header="_Help">
<MenuItem Header="Artemis wiki" <MenuItem Header="Artemis Wiki"
Icon="{materialDesign:PackIcon Kind=BookEdit}" Icon="{materialDesign:PackIcon Kind=BookEdit}"
Command="{s:Action OpenUrl}" Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com/" /> CommandParameter="https://wiki.artemis-rgb.com/" />
@ -121,7 +152,7 @@
Icon="{materialDesign:PackIcon Kind=Layers}" Icon="{materialDesign:PackIcon Kind=Layers}"
Command="{s:Action OpenUrl}" Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/layers" /> CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/layers" />
<MenuItem Header="Display conditions" <MenuItem Header="Display Conditions"
Icon="{materialDesign:PackIcon Kind=NotEqual}" Icon="{materialDesign:PackIcon Kind=NotEqual}"
Command="{s:Action OpenUrl}" Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/conditions" /> CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/conditions" />
@ -129,7 +160,7 @@
Icon="{materialDesign:PackIcon Kind=Stopwatch}" Icon="{materialDesign:PackIcon Kind=Stopwatch}"
Command="{s:Action OpenUrl}" Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/timeline" /> CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/timeline" />
<MenuItem Header="Data bindings" <MenuItem Header="Data Bindings"
Icon="{materialDesign:PackIcon Kind=VectorLink}" Icon="{materialDesign:PackIcon Kind=VectorLink}"
Command="{s:Action OpenUrl}" Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/data-bindings" /> CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/data-bindings" />
@ -138,11 +169,11 @@
Command="{s:Action OpenUrl}" Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/scripting" /> CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/scripting" />
<Separator /> <Separator />
<MenuItem Header="Report a bug" <MenuItem Header="Report a Bug"
Icon="{materialDesign:PackIcon Kind=Github}" Icon="{materialDesign:PackIcon Kind=Github}"
Command="{s:Action OpenUrl}" Command="{s:Action OpenUrl}"
CommandParameter="https://github.com/Artemis-RGB/Artemis/issues" /> CommandParameter="https://github.com/Artemis-RGB/Artemis/issues" />
<MenuItem Header="Get help on Discord" <MenuItem Header="Get Help on Discord"
Icon="{materialDesign:PackIcon Kind=Discord}" Icon="{materialDesign:PackIcon Kind=Discord}"
Command="{s:Action OpenUrl}" Command="{s:Action OpenUrl}"
CommandParameter="https://discord.gg/S3MVaC9" /> CommandParameter="https://discord.gg/S3MVaC9" />
@ -152,7 +183,7 @@
<Button Grid.Row="0" <Button Grid.Row="0"
Grid.Column="2" Grid.Column="2"
Style="{StaticResource MaterialDesignIconForegroundButton}" Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Open debugger" ToolTip="Open Debugger"
Margin="10 -4 10 0" Margin="10 -4 10 0"
Width="34" Width="34"
Height="34" Height="34"

View File

@ -1,24 +1,28 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Events;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.DisplayConditions; using Artemis.UI.Screens.ProfileEditor.DisplayConditions;
using Artemis.UI.Screens.ProfileEditor.LayerProperties; using Artemis.UI.Screens.ProfileEditor.LayerProperties;
using Artemis.UI.Screens.ProfileEditor.ProfileTree; using Artemis.UI.Screens.ProfileEditor.ProfileTree;
using Artemis.UI.Screens.ProfileEditor.Visualization; using Artemis.UI.Screens.ProfileEditor.Visualization;
using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Services; using Artemis.UI.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf;
using Stylet; using Stylet;
using ProfileConfigurationEventArgs = Artemis.UI.Shared.ProfileConfigurationEventArgs; using ProfileConfigurationEventArgs = Artemis.UI.Shared.ProfileConfigurationEventArgs;
namespace Artemis.UI.Screens.ProfileEditor namespace Artemis.UI.Screens.ProfileEditor
{ {
public class ProfileEditorViewModel : MainScreenViewModel public class ProfileEditorViewModel : MainScreenViewModel, IHandle<MainWindowFocusChangedEvent>
{ {
private readonly IDebugService _debugService; private readonly IDebugService _debugService;
private readonly IMessageService _messageService; private readonly IMessageService _messageService;
@ -28,14 +32,12 @@ namespace Artemis.UI.Screens.ProfileEditor
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly ISidebarVmFactory _sidebarVmFactory; private readonly ISidebarVmFactory _sidebarVmFactory;
private readonly IWindowManager _windowManager; private readonly IWindowManager _windowManager;
private PluginSetting<GridLength> _bottomPanelsHeight; private readonly IEventAggregator _eventAggregator;
private PluginSetting<GridLength> _dataModelConditionsHeight;
private DisplayConditionsViewModel _displayConditionsViewModel; private DisplayConditionsViewModel _displayConditionsViewModel;
private PluginSetting<GridLength> _elementPropertiesWidth;
private LayerPropertiesViewModel _layerPropertiesViewModel; private LayerPropertiesViewModel _layerPropertiesViewModel;
private ProfileTreeViewModel _profileTreeViewModel; private ProfileTreeViewModel _profileTreeViewModel;
private ProfileViewModel _profileViewModel; private ProfileViewModel _profileViewModel;
private PluginSetting<GridLength> _sidePanelsWidth; private bool _suspendedManually;
public ProfileEditorViewModel(ProfileViewModel profileViewModel, public ProfileEditorViewModel(ProfileViewModel profileViewModel,
ProfileTreeViewModel profileTreeViewModel, ProfileTreeViewModel profileTreeViewModel,
@ -48,6 +50,7 @@ namespace Artemis.UI.Screens.ProfileEditor
IMessageService messageService, IMessageService messageService,
IDebugService debugService, IDebugService debugService,
IWindowManager windowManager, IWindowManager windowManager,
IEventAggregator eventAggregator,
IScriptVmFactory scriptVmFactory, IScriptVmFactory scriptVmFactory,
ISidebarVmFactory sidebarVmFactory) ISidebarVmFactory sidebarVmFactory)
{ {
@ -57,6 +60,7 @@ namespace Artemis.UI.Screens.ProfileEditor
_messageService = messageService; _messageService = messageService;
_debugService = debugService; _debugService = debugService;
_windowManager = windowManager; _windowManager = windowManager;
_eventAggregator = eventAggregator;
_scriptVmFactory = scriptVmFactory; _scriptVmFactory = scriptVmFactory;
_sidebarVmFactory = sidebarVmFactory; _sidebarVmFactory = sidebarVmFactory;
@ -102,43 +106,20 @@ namespace Artemis.UI.Screens.ProfileEditor
set => SetAndNotify(ref _profileViewModel, value); set => SetAndNotify(ref _profileViewModel, value);
} }
public PluginSetting<GridLength> SidePanelsWidth public bool SuspendedManually
{ {
get => _sidePanelsWidth; get => _suspendedManually;
set => SetAndNotify(ref _sidePanelsWidth, value); set => SetAndNotify(ref _suspendedManually, value);
} }
public PluginSetting<GridLength> DataModelConditionsHeight public PluginSetting<GridLength> SidePanelsWidth => _settingsService.GetSetting("ProfileEditor.SidePanelsWidth", new GridLength(385));
{ public PluginSetting<GridLength> DataModelConditionsHeight => _settingsService.GetSetting("ProfileEditor.DataModelConditionsHeight", new GridLength(345));
get => _dataModelConditionsHeight; public PluginSetting<GridLength> BottomPanelsHeight => _settingsService.GetSetting("ProfileEditor.BottomPanelsHeight", new GridLength(265));
set => SetAndNotify(ref _dataModelConditionsHeight, value); public PluginSetting<GridLength> ElementPropertiesWidth => _settingsService.GetSetting("ProfileEditor.ElementPropertiesWidth", new GridLength(545));
} public PluginSetting<bool> StopOnFocusLoss => _settingsService.GetSetting("ProfileEditor.StopOnFocusLoss", true);
public PluginSetting<bool> ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
public PluginSetting<GridLength> BottomPanelsHeight public PluginSetting<bool> FocusSelectedLayer => _settingsService.GetSetting("ProfileEditor.FocusSelectedLayer", true);
{ public PluginSetting<bool> AlwaysApplyDataBindings => _settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true);
get => _bottomPanelsHeight;
set => SetAndNotify(ref _bottomPanelsHeight, value);
}
public PluginSetting<GridLength> ElementPropertiesWidth
{
get => _elementPropertiesWidth;
set => SetAndNotify(ref _elementPropertiesWidth, value);
}
public async Task AdaptActiveProfile()
{
if (_profileEditorService.SelectedProfileConfiguration?.Profile == null)
return;
if (!await DialogService.ShowConfirmDialog(
"Adapt profile",
"Are you sure you want to adapt the profile to your current surface? Layer assignments may change."
))
return;
_profileService.AdaptProfile(_profileEditorService.SelectedProfileConfiguration.Profile);
}
public void Undo() public void Undo()
{ {
@ -194,25 +175,52 @@ namespace Artemis.UI.Screens.ProfileEditor
_messageService.ShowMessage("Redid profile update", "UNDO", Undo); _messageService.ShowMessage("Redid profile update", "UNDO", Undo);
} }
public void ToggleSuspend()
{
_profileEditorService.SuspendEditing = !_profileEditorService.SuspendEditing;
SuspendedManually = _profileEditorService.SuspendEditing;
}
public void ToggleAutoSuspend()
{
StopOnFocusLoss.Value = !StopOnFocusLoss.Value;
}
#region Overrides of Screen
protected override void OnInitialActivate() protected override void OnInitialActivate()
{ {
StopOnFocusLoss.AutoSave = true;
ShowDataModelValues.AutoSave = true;
FocusSelectedLayer.AutoSave = true;
AlwaysApplyDataBindings.AutoSave = true;
_profileEditorService.SelectedProfileChanged += ProfileEditorServiceOnSelectedProfileChanged; _profileEditorService.SelectedProfileChanged += ProfileEditorServiceOnSelectedProfileChanged;
_profileEditorService.SelectedProfileElementChanged += ProfileEditorServiceOnSelectedProfileElementChanged; _profileEditorService.SelectedProfileElementChanged += ProfileEditorServiceOnSelectedProfileElementChanged;
LoadWorkspaceSettings(); _eventAggregator.Subscribe(this);
base.OnInitialActivate(); base.OnInitialActivate();
} }
protected override void OnClose() protected override void OnClose()
{ {
StopOnFocusLoss.AutoSave = false;
ShowDataModelValues.AutoSave = false;
FocusSelectedLayer.AutoSave = false;
AlwaysApplyDataBindings.AutoSave = false;
_profileEditorService.SelectedProfileChanged -= ProfileEditorServiceOnSelectedProfileChanged; _profileEditorService.SelectedProfileChanged -= ProfileEditorServiceOnSelectedProfileChanged;
_profileEditorService.SelectedProfileElementChanged -= ProfileEditorServiceOnSelectedProfileElementChanged; _profileEditorService.SelectedProfileElementChanged -= ProfileEditorServiceOnSelectedProfileElementChanged;
_eventAggregator.Unsubscribe(this);
SaveWorkspaceSettings(); SaveWorkspaceSettings();
_profileEditorService.SuspendEditing = false;
_profileEditorService.ChangeSelectedProfileConfiguration(null); _profileEditorService.ChangeSelectedProfileConfiguration(null);
base.OnClose(); base.OnClose();
} }
#endregion
private void ProfileEditorServiceOnSelectedProfileChanged(object sender, ProfileConfigurationEventArgs e) private void ProfileEditorServiceOnSelectedProfileChanged(object sender, ProfileConfigurationEventArgs e)
{ {
NotifyOfPropertyChange(nameof(ProfileConfiguration)); NotifyOfPropertyChange(nameof(ProfileConfiguration));
@ -223,14 +231,6 @@ namespace Artemis.UI.Screens.ProfileEditor
NotifyOfPropertyChange(nameof(HasSelectedElement)); 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() private void SaveWorkspaceSettings()
{ {
SidePanelsWidth.Save(); SidePanelsWidth.Save();
@ -249,6 +249,20 @@ namespace Artemis.UI.Screens.ProfileEditor
await _sidebarVmFactory.SidebarProfileConfigurationViewModel(_profileEditorService.SelectedProfileConfiguration).ViewProperties(); await _sidebarVmFactory.SidebarProfileConfigurationViewModel(_profileEditorService.SelectedProfileConfiguration).ViewProperties();
} }
public async Task AdaptProfile()
{
if (_profileEditorService.SelectedProfileConfiguration?.Profile == null)
return;
if (!await DialogService.ShowConfirmDialog(
"Adapt profile",
"Are you sure you want to adapt the profile to your current surface? Layer assignments may change."
))
return;
_profileService.AdaptProfile(_profileEditorService.SelectedProfileConfiguration.Profile);
}
public void DuplicateProfile() public void DuplicateProfile()
{ {
ProfileConfigurationExportModel export = _profileService.ExportProfile(ProfileConfiguration); ProfileConfigurationExportModel export = _profileService.ExportProfile(ProfileConfiguration);
@ -322,5 +336,18 @@ namespace Artemis.UI.Screens.ProfileEditor
} }
#endregion #endregion
#region Implementation of IHandle<in MainWindowFocusChangedEvent>
/// <inheritdoc />
public void Handle(MainWindowFocusChangedEvent message)
{
if (!StopOnFocusLoss.Value || SuspendedManually)
return;
_profileEditorService.SuspendEditing = !message.IsFocused;
}
#endregion
} }
} }

View File

@ -97,6 +97,11 @@
<b:Interaction.Behaviors> <b:Interaction.Behaviors>
<behaviors:TreeViewSelectionBehavior SelectedItem="{Binding SelectedTreeItem}" /> <behaviors:TreeViewSelectionBehavior SelectedItem="{Binding SelectedTreeItem}" />
</b:Interaction.Behaviors> </b:Interaction.Behaviors>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource MaterialDesignTreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay, FallbackValue=False}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources> <TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type treeItem:FolderViewModel}" ItemsSource="{Binding Items}"> <HierarchicalDataTemplate DataType="{x:Type treeItem:FolderViewModel}" ItemsSource="{Binding Items}">
<ContentControl s:View.Model="{Binding}" /> <ContentControl s:View.Model="{Binding}" />

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using Artemis.Core; using Artemis.Core;
@ -24,8 +25,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
{ {
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_profileTreeVmFactory = profileTreeVmFactory; _profileTreeVmFactory = profileTreeVmFactory;
CreateRootFolderViewModel();
} }
public TreeItemViewModel SelectedTreeItem public TreeItemViewModel SelectedTreeItem
@ -33,7 +32,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
get => _selectedTreeItem; get => _selectedTreeItem;
set set
{ {
if (!_updatingTree && SetAndNotify(ref _selectedTreeItem, value) && !_draggingTreeView) if (!_updatingTree && SetAndNotify(ref _selectedTreeItem, value) && !_draggingTreeView)
ApplySelectedTreeItem(); ApplySelectedTreeItem();
} }
} }
@ -54,6 +53,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
protected override void OnInitialActivate() protected override void OnInitialActivate()
{ {
Subscribe(); Subscribe();
CreateRootFolderViewModel();
base.OnInitialActivate(); base.OnInitialActivate();
} }
@ -83,6 +83,14 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
ActiveItem = _profileTreeVmFactory.FolderViewModel(folder); ActiveItem = _profileTreeVmFactory.FolderViewModel(folder);
_updatingTree = false; _updatingTree = false;
Execute.PostToUIThread(async () =>
{
await Task.Delay(1500);
if (ActiveItem != null)
SelectedTreeItem = ActiveItem.GetAllChildren().FirstOrDefault(c => c.ProfileElement == _profileEditorService.SelectedProfileElement);
});
} }
#region IDropTarget #region IDropTarget

View File

@ -1,4 +1,5 @@
using Artemis.Core; using System.ComponentModel;
using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
@ -19,5 +20,35 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
} }
public override bool SupportsChildren => true; 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
/// <inheritdoc />
protected override void OnInitialActivate()
{
ProfileElement.PropertyChanged += ProfileElementOnPropertyChanged;
base.OnInitialActivate();
}
/// <inheritdoc />
protected override void OnClose()
{
ProfileElement.PropertyChanged -= ProfileElementOnPropertyChanged;
base.OnClose();
}
#endregion
} }
} }

View File

@ -33,5 +33,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
public Layer Layer => ProfileElement as Layer; public Layer Layer => ProfileElement as Layer;
public bool ShowIcons => Layer?.LayerBrush != null; public bool ShowIcons => Layer?.LayerBrush != null;
public override bool SupportsChildren => false; public override bool SupportsChildren => false;
public override bool IsExpanded { get; set; }
} }
} }

View File

@ -52,6 +52,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
public bool CanPasteElement => _profileEditorService.GetCanPasteProfileElement(); public bool CanPasteElement => _profileEditorService.GetCanPasteProfileElement();
public abstract bool SupportsChildren { get; } public abstract bool SupportsChildren { get; }
public abstract bool IsExpanded { get; set; }
public List<TreeItemViewModel> GetAllChildren() public List<TreeItemViewModel> GetAllChildren()
{ {

View File

@ -0,0 +1,23 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.SuspendedProfileEditorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Top" Margin="16">
<materialDesign:PackIcon Kind="PauseCircle" Width="250" Height="250" HorizontalAlignment="Center" />
<TextBlock Style="{StaticResource MaterialDesignHeadline4TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0 25">
Profile editing has been paused
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center">
Artemis has resumed regular profile playback. As soon as you focus this window, the editor will open to continue editing
'<Run Text="{Binding PreviousSelectedProfileConfiguration.Name}" />'.
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" HorizontalAlignment="Center">
You can disable this behaviour in the editor's option menu.
</TextBlock>
</StackPanel>
</UserControl>

View File

@ -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<MainWindowFocusChangedEvent>
{
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
}
}

View File

@ -1,4 +1,4 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Visualization.ProfileView" <UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -7,9 +7,13 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:visualization="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization" xmlns:visualization="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:Converters="clr-namespace:Artemis.UI.Converters" x:Class="Artemis.UI.Screens.ProfileEditor.Visualization.ProfileView"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="510.9" d:DesignWidth="800" d:DesignHeight="510.9" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type visualization:ProfileViewModel}}"> d:DataContext="{d:DesignInstance {x:Type visualization:ProfileViewModel}}">
<UserControl.Resources>
<Converters:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
</UserControl.Resources>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@ -27,13 +31,16 @@
<ListBoxItem ToolTip="Pan over different parts of the surface - Ctrl"> <ListBoxItem ToolTip="Pan over different parts of the surface - Ctrl">
<materialDesign:PackIcon Kind="HandLeft" /> <materialDesign:PackIcon Kind="HandLeft" />
</ListBoxItem> </ListBoxItem>
<ListBoxItem ToolTip="Transform layer shape (hold SHIFT for incremental changes and X/Y snapping) - Ctrl+T" IsEnabled="{Binding CanSelectEditTool}"> <ListBoxItem ToolTip="Transform layer shape (hold SHIFT for incremental changes and X/Y snapping) - Ctrl+T"
IsEnabled="{Binding CanSelectEditTool}">
<materialDesign:PackIcon Kind="TransitConnectionVariant" /> <materialDesign:PackIcon Kind="TransitConnectionVariant" />
</ListBoxItem> </ListBoxItem>
<ListBoxItem ToolTip="Change layer selection (hold SHIFT to add to existing selection) - Ctrl+Q"> <ListBoxItem ToolTip="Change layer selection (hold SHIFT to add to existing selection) - Ctrl+Q"
IsEnabled="{Binding SuspendedEditing, Converter={StaticResource InverseBooleanConverter}, Mode=OneWay}">
<materialDesign:PackIcon Kind="SelectionDrag" /> <materialDesign:PackIcon Kind="SelectionDrag" />
</ListBoxItem> </ListBoxItem>
<ListBoxItem ToolTip="Remove from layer selection - Ctrl+W"> <ListBoxItem ToolTip="Remove from layer selection - Ctrl+W"
IsEnabled="{Binding SuspendedEditing, Converter={StaticResource InverseBooleanConverter}, Mode=OneWay}">
<materialDesign:PackIcon Kind="SelectOff" /> <materialDesign:PackIcon Kind="SelectOff" />
</ListBoxItem> </ListBoxItem>
</ListBox> </ListBox>
@ -71,7 +78,7 @@
</VisualBrush> </VisualBrush>
</Grid.Background> </Grid.Background>
<Grid Name="DeviceDisplayGrid"> <Grid x:Name="DeviceDisplayGrid">
<Grid.RenderTransform> <Grid.RenderTransform>
<TransformGroup> <TransformGroup>
<ScaleTransform ScaleX="{Binding PanZoomViewModel.Zoom}" ScaleY="{Binding PanZoomViewModel.Zoom}" /> <ScaleTransform ScaleX="{Binding PanZoomViewModel.Zoom}" ScaleY="{Binding PanZoomViewModel.Zoom}" />
@ -85,7 +92,7 @@
</ItemsPanelTemplate> </ItemsPanelTemplate>
</ItemsControl.ItemsPanel> </ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle> <ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter"> <Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Canvas.Left" Value="{Binding X}" /> <Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" /> <Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style> </Style>
@ -94,13 +101,15 @@
<DataTemplate> <DataTemplate>
<shared:DeviceVisualizer Device="{Binding}" <shared:DeviceVisualizer Device="{Binding}"
ShowColors="True" ShowColors="True"
HighlightedLeds="{Binding DataContext.HighlightedLeds, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Mode=OneWay}" /> RenderOptions.BitmapScalingMode="HighQuality"
HighlightedLeds="{Binding DataContext.HighlightedLeds, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
</Grid> </Grid>
<Grid Name="EditorDisplayGrid" <Grid x:Name="EditorDisplayGrid"
Visibility="{Binding SuspendedEditing, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}"
shared:SizeObserver.Observe="True" shared:SizeObserver.Observe="True"
shared:SizeObserver.ObservedHeight="{Binding PanZoomViewModel.CanvasHeight}" shared:SizeObserver.ObservedHeight="{Binding PanZoomViewModel.CanvasHeight}"
shared:SizeObserver.ObservedWidth="{Binding PanZoomViewModel.CanvasWidth}"> shared:SizeObserver.ObservedWidth="{Binding PanZoomViewModel.CanvasWidth}">
@ -117,7 +126,7 @@
</ItemsPanelTemplate> </ItemsPanelTemplate>
</ItemsControl.ItemsPanel> </ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle> <ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter"> <Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Canvas.Left" Value="{Binding X}" /> <Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" /> <Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style> </Style>
@ -130,26 +139,8 @@
</ItemsControl> </ItemsControl>
</Grid> </Grid>
<StackPanel ZIndex="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10" Cursor="Arrow">
<materialDesign:Card Padding="8">
<StackPanel Orientation="Horizontal">
<CheckBox Style="{StaticResource MaterialDesignCheckBox}"
IsChecked="{Binding FocusSelectedLayer.Value}"
ToolTip="If selected, dims all LEDs that are not part of the selected layer">
Focus selected layer
</CheckBox>
<CheckBox Style="{StaticResource MaterialDesignCheckBox}"
Margin="10 0 0 0"
IsChecked="{Binding AlwaysApplyDataBindings.Value}"
ToolTip="If selected, updates all data bindings instead of only the one you are editing">
Apply all data bindings in editor
</CheckBox>
</StackPanel>
</materialDesign:Card>
</StackPanel>
<StackPanel Orientation="Vertical" VerticalAlignment="Bottom" HorizontalAlignment="Right" <StackPanel Orientation="Vertical" VerticalAlignment="Bottom" HorizontalAlignment="Right"
Margin="10" ZIndex="1"> Margin="10" Panel.ZIndex="1">
<Slider Orientation="Vertical" <Slider Orientation="Vertical"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Margin="0 10" Margin="0 10"

View File

@ -26,12 +26,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
private int _activeToolIndex; private int _activeToolIndex;
private VisualizationToolViewModel _activeToolViewModel; private VisualizationToolViewModel _activeToolViewModel;
private PluginSetting<bool> _alwaysApplyDataBindings;
private bool _canApplyToLayer; private bool _canApplyToLayer;
private bool _canSelectEditTool; private bool _canSelectEditTool;
private BindableCollection<ArtemisDevice> _devices; private BindableCollection<ArtemisDevice> _devices;
private BindableCollection<ArtemisLed> _highlightedLeds; private BindableCollection<ArtemisLed> _highlightedLeds;
private PluginSetting<bool> _focusSelectedLayer;
private DateTime _lastUpdate; private DateTime _lastUpdate;
private PanZoomViewModel _panZoomViewModel; private PanZoomViewModel _panZoomViewModel;
private Layer _previousSelectedLayer; private Layer _previousSelectedLayer;
@ -79,18 +77,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
set => SetAndNotify(ref _highlightedLeds, value); set => SetAndNotify(ref _highlightedLeds, value);
} }
public PluginSetting<bool> AlwaysApplyDataBindings
{
get => _alwaysApplyDataBindings;
set => SetAndNotify(ref _alwaysApplyDataBindings, value);
}
public PluginSetting<bool> FocusSelectedLayer
{
get => _focusSelectedLayer;
set => SetAndNotify(ref _focusSelectedLayer, value);
}
public VisualizationToolViewModel ActiveToolViewModel public VisualizationToolViewModel ActiveToolViewModel
{ {
get => _activeToolViewModel; get => _activeToolViewModel;
@ -132,9 +118,17 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
set => SetAndNotify(ref _canApplyToLayer, value); set => SetAndNotify(ref _canApplyToLayer, value);
} }
public bool SuspendedEditing => _profileEditorService.SuspendEditing;
protected override void OnInitialActivate() 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<ArtemisDevice>(); Devices = new BindableCollection<ArtemisDevice>();
HighlightedLeds = new BindableCollection<ArtemisLed>(); HighlightedLeds = new BindableCollection<ArtemisLed>();
@ -144,15 +138,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
ApplyActiveProfile(); ApplyActiveProfile();
AlwaysApplyDataBindings = _settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true);
FocusSelectedLayer = _settingsService.GetSetting("ProfileEditor.FocusSelectedLayer", true);
_lastUpdate = DateTime.Now; _lastUpdate = DateTime.Now;
_coreService.FrameRendered += OnFrameRendered; _coreService.FrameRendered += OnFrameRendered;
FocusSelectedLayer.SettingChanged += HighlightSelectedLayerOnSettingChanged;
_rgbService.DeviceAdded += RgbServiceOnDevicesModified; _rgbService.DeviceAdded += RgbServiceOnDevicesModified;
_rgbService.DeviceRemoved += RgbServiceOnDevicesModified; _rgbService.DeviceRemoved += RgbServiceOnDevicesModified;
_profileEditorService.SuspendEditingChanged += ProfileEditorServiceOnSuspendEditingChanged;
_profileEditorService.SelectedProfileChanged += OnSelectedProfileChanged; _profileEditorService.SelectedProfileChanged += OnSelectedProfileChanged;
_profileEditorService.SelectedProfileElementChanged += OnSelectedProfileElementChanged; _profileEditorService.SelectedProfileElementChanged += OnSelectedProfileElementChanged;
_profileEditorService.SelectedProfileElementSaved += OnSelectedProfileElementSaved; _profileEditorService.SelectedProfileElementSaved += OnSelectedProfileElementSaved;
@ -163,17 +154,21 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
protected override void OnClose() protected override void OnClose()
{ {
_coreService.FrameRendered -= OnFrameRendered; _coreService.FrameRendered -= OnFrameRendered;
FocusSelectedLayer.SettingChanged -= HighlightSelectedLayerOnSettingChanged;
_rgbService.DeviceAdded -= RgbServiceOnDevicesModified; _rgbService.DeviceAdded -= RgbServiceOnDevicesModified;
_rgbService.DeviceRemoved -= RgbServiceOnDevicesModified; _rgbService.DeviceRemoved -= RgbServiceOnDevicesModified;
_profileEditorService.SuspendEditingChanged -= ProfileEditorServiceOnSuspendEditingChanged;
_profileEditorService.SelectedProfileChanged -= OnSelectedProfileChanged; _profileEditorService.SelectedProfileChanged -= OnSelectedProfileChanged;
_profileEditorService.SelectedProfileElementChanged -= OnSelectedProfileElementChanged; _profileEditorService.SelectedProfileElementChanged -= OnSelectedProfileElementChanged;
_profileEditorService.SelectedProfileElementSaved -= OnSelectedProfileElementSaved; _profileEditorService.SelectedProfileElementSaved -= OnSelectedProfileElementSaved;
if (_previousSelectedLayer != null) if (_previousSelectedLayer != null)
_previousSelectedLayer.LayerBrushUpdated -= SelectedLayerOnLayerBrushUpdated; _previousSelectedLayer.LayerBrushUpdated -= SelectedLayerOnLayerBrushUpdated;
AlwaysApplyDataBindings.Save(); _settingsService.GetSetting("ProfileEditor.PanX", 0d).Value = PanZoomViewModel.PanX;
FocusSelectedLayer.Save(); _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(); base.OnClose();
} }
@ -210,12 +205,19 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
private void UpdateLedsDimStatus() private void UpdateLedsDimStatus()
{ {
HighlightedLeds.Clear(); HighlightedLeds.Clear();
if (FocusSelectedLayer.Value && _profileEditorService.SelectedProfileElement is Layer layer) if (_profileEditorService.SelectedProfileElement is Layer layer)
HighlightedLeds.AddRange(layer.Leds); HighlightedLeds.AddRange(layer.Leds);
} }
private void UpdateCanSelectEditTool() private void UpdateCanSelectEditTool()
{ {
if (SuspendedEditing)
{
CanApplyToLayer = false;
CanSelectEditTool = false;
return;
}
if (_profileEditorService.SelectedProfileElement is Layer layer) if (_profileEditorService.SelectedProfileElement is Layer layer)
{ {
CanApplyToLayer = true; CanApplyToLayer = true;
@ -230,14 +232,14 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
if (CanSelectEditTool == false && ActiveToolIndex == 1) if (CanSelectEditTool == false && ActiveToolIndex == 1)
ActivateToolByIndex(2); ActivateToolByIndex(2);
} }
#region Buttons #region Buttons
private void ActivateToolByIndex(int value) private void ActivateToolByIndex(int value)
{ {
if (value == 1 && !CanSelectEditTool) if (value == 1 && !CanSelectEditTool)
return; return;
switch (value) switch (value)
{ {
case 0: case 0:
@ -308,7 +310,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
TimeSpan delta = DateTime.Now - _lastUpdate; TimeSpan delta = DateTime.Now - _lastUpdate;
_lastUpdate = DateTime.Now; _lastUpdate = DateTime.Now;
if (!AlwaysApplyDataBindings.Value || _profileEditorService.SelectedProfile == null) if (SuspendedEditing || !_settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true).Value || _profileEditorService.SelectedProfile == null)
return; return;
foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllFolders() foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllFolders()
@ -389,6 +391,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
ActivateToolByIndex(2); ActivateToolByIndex(2);
} }
private void ProfileEditorServiceOnSuspendEditingChanged(object? sender, EventArgs e)
{
NotifyOfPropertyChange(nameof(SuspendedEditing));
UpdateCanSelectEditTool();
}
public void Handle(MainWindowKeyEvent message) public void Handle(MainWindowKeyEvent message)
{ {
if (message.KeyDown) if (message.KeyDown)

View File

@ -32,6 +32,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools
{ {
base.MouseUp(sender, e); base.MouseUp(sender, e);
if (ProfileEditorService.SuspendEditing)
return;
Point position = PanZoomViewModel.GetRelativeMousePosition(sender, e); Point position = PanZoomViewModel.GetRelativeMousePosition(sender, e);
Rect selectedRect = new(MouseDownStartPosition, position); Rect selectedRect = new(MouseDownStartPosition, position);

View File

@ -37,6 +37,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools
{ {
base.MouseUp(sender, e); base.MouseUp(sender, e);
if (ProfileEditorService.SuspendEditing)
return;
Point position = PanZoomViewModel.GetRelativeMousePosition(sender, e); Point position = PanZoomViewModel.GetRelativeMousePosition(sender, e);
Rect selectedRect = new(MouseDownStartPosition, position); Rect selectedRect = new(MouseDownStartPosition, position);

View File

@ -20,6 +20,7 @@
UseLayoutRounding="True" UseLayoutRounding="True"
Deactivated="{s:Action WindowDeactivated}" Deactivated="{s:Action WindowDeactivated}"
Activated="{s:Action WindowActivated}" Activated="{s:Action WindowActivated}"
StateChanged="{s:Action WindowStateChanged}"
KeyDown="{s:Action WindowKeyDown}" KeyDown="{s:Action WindowKeyDown}"
KeyUp="{s:Action WindowKeyUp}" KeyUp="{s:Action WindowKeyUp}"
MouseDown="{s:Action WindowMouseDown}" MouseDown="{s:Action WindowMouseDown}"

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Reflection; using System.Reflection;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
@ -15,6 +16,7 @@ using MaterialDesignExtensions.Controls;
using MaterialDesignThemes.Wpf; using MaterialDesignThemes.Wpf;
using Ninject; using Ninject;
using Stylet; using Stylet;
using Stylet.Xaml;
using Constants = Artemis.Core.Constants; using Constants = Artemis.Core.Constants;
namespace Artemis.UI.Screens namespace Artemis.UI.Screens
@ -78,8 +80,7 @@ namespace Artemis.UI.Screens
public void WindowDeactivated() public void WindowDeactivated()
{ {
WindowState windowState = ((Window) View).WindowState; if (_lostFocus)
if (windowState == WindowState.Minimized)
return; return;
_lostFocus = true; _lostFocus = true;
@ -95,6 +96,25 @@ namespace Artemis.UI.Screens
_eventAggregator.Publish(new MainWindowFocusChangedEvent(true)); _eventAggregator.Publish(new MainWindowFocusChangedEvent(true));
} }
// This is required because Windows incorrectly tells the window it is activated when minimizing using the taskbar
public void WindowStateChanged()
{
if (((Window) View).WindowState == WindowState.Minimized)
WindowDeactivated();
else
WindowActivated();
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnStateChanged(ScreenState previousState, ScreenState newState)
{
base.OnStateChanged(previousState, newState);
}
#endregion
public void WindowKeyDown(object sender, KeyEventArgs e) public void WindowKeyDown(object sender, KeyEventArgs e)
{ {
_eventAggregator.Publish(new MainWindowKeyEvent(sender, true, e)); _eventAggregator.Publish(new MainWindowKeyEvent(sender, true, e));

View File

@ -12,6 +12,7 @@
d:DataContext="{d:DesignInstance {x:Type tabs:DeviceLedsTabViewModel}}"> d:DataContext="{d:DesignInstance {x:Type tabs:DeviceLedsTabViewModel}}">
<UserControl.Resources> <UserControl.Resources>
<converters:UriToFileNameConverter x:Key="UriToFileNameConverter" /> <converters:UriToFileNameConverter x:Key="UriToFileNameConverter" />
<converters:LedIdToStringConverter x:Key="LedIdToStringConverter" />
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid>
<DataGrid ItemsSource="{Binding LedViewModels}" <DataGrid ItemsSource="{Binding LedViewModels}"
@ -31,7 +32,7 @@
</Style> </Style>
</DataGrid.Resources> </DataGrid.Resources>
<DataGrid.Columns> <DataGrid.Columns>
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Id}" Header="LED ID" Width="Auto" /> <materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Id, Converter={StaticResource LedIdToStringConverter}, Mode=OneWay}" Header="LED ID" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Color}" Header="Color (ARGB)" Width="Auto" CanUserSort="False" /> <materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Color}" Header="Color (ARGB)" Width="Auto" CanUserSort="False" />
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.Layout.Image, Converter={StaticResource UriToFileNameConverter}, Mode=OneWay}" Header="Image file" CanUserSort="False" /> <materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.Layout.Image, Converter={StaticResource UriToFileNameConverter}, Mode=OneWay}" Header="Image file" CanUserSort="False" />
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Shape}" Header="Shape" /> <materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Shape}" Header="Shape" />

View File

@ -126,7 +126,12 @@ namespace Artemis.UI.Screens.Sidebar
public void Handle(RequestSelectSidebarItemEvent message) 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) if (requested != null)
SelectedSidebarScreen = requested; SelectedSidebarScreen = requested;
} }
@ -148,6 +153,8 @@ namespace Artemis.UI.Screens.Sidebar
foreach (SidebarCategoryViewModel sidebarCategoryViewModel in Items) foreach (SidebarCategoryViewModel sidebarCategoryViewModel in Items)
sidebarCategoryViewModel.SelectedProfileConfiguration = sidebarCategoryViewModel.Items.FirstOrDefault(i => i.ProfileConfiguration == profileConfiguration); sidebarCategoryViewModel.SelectedProfileConfiguration = sidebarCategoryViewModel.Items.FirstOrDefault(i => i.ProfileConfiguration == profileConfiguration);
if (_profileEditorService.SuspendEditing)
_profileEditorService.SuspendEditing = false;
_profileEditorService.ChangeSelectedProfileConfiguration(profileConfiguration); _profileEditorService.ChangeSelectedProfileConfiguration(profileConfiguration);
if (profileConfiguration != null) if (profileConfiguration != null)
{ {

View File

@ -33,7 +33,11 @@
Visibility="{Binding Device.Layout.Image, ConverterParameter=Inverted, Converter={StaticResource NullToVisibilityConverter}}" Visibility="{Binding Device.Layout.Image, ConverterParameter=Inverted, Converter={StaticResource NullToVisibilityConverter}}"
IsHitTestVisible="False"/> IsHitTestVisible="False"/>
<shared:DeviceVisualizer Device="{Binding Device}" ShowColors="True" VerticalAlignment="Top" HorizontalAlignment="Left" /> <shared:DeviceVisualizer Device="{Binding Device}"
RenderOptions.BitmapScalingMode="HighQuality"
ShowColors="True"
VerticalAlignment="Top"
HorizontalAlignment="Left" />
</Grid> </Grid>